diff --git a/Dockerfile b/Dockerfile index 827325c131c57d542e95ec6f989d83e6ce513afc..a6dbb0e1a8e25e556341bbc5867908c14e12f0ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,28 @@ -FROM openjdk:21-jdk-slim - -WORKDIR /app - -# Install required packages -RUN apt-get update && apt-get install -y \ - curl \ - wget \ - && rm -rf /var/lib/apt/lists/* - -# Copy application files -COPY . . - -# Build application (if build.gradle.kts exists) -RUN if [ -f "build.gradle.kts" ]; then \ - ./gradlew build -x test; \ - fi - -# Expose port -EXPOSE 8080 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:8080/actuator/health || exit 1 - -# Run application -CMD ["java", "-jar", "build/libs/da-policyengine.jar"] +FROM openjdk:21-jdk-slim + +WORKDIR /app + +# Install required packages +RUN apt-get update && apt-get install -y \ + curl \ + wget \ + && rm -rf /var/lib/apt/lists/* + +# Copy application files +COPY . . + +# Make gradlew executable +RUN chmod +x ./gradlew + +# Build application +RUN ./gradlew build -x test + +# Expose port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8080/actuator/health || exit 1 + +# Run application +CMD ["java", "-jar", "build/libs/da-policyengine.jar"] diff --git a/README.md b/README.md index 382050ee8ce1ace00f39ec2af88e41d7b0be7bcc..c8f0164c76ebd62c763a6d30ecab60c38dfbfe28 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,38 @@ ---- -title: da-policyengine (dev) -emoji: 🔧 -colorFrom: blue -colorTo: green -sdk: docker -app_port: 8080 ---- - -# da-policyengine - dev Environment - -This is the da-policyengine microservice deployed in the dev environment. - -## Features - -- RESTful API endpoints -- Health monitoring via Actuator -- JWT authentication integration -- PostgreSQL database connectivity - -## API Documentation - -Once deployed, API documentation will be available at: -- Swagger UI: https://huggingface.co/spaces/dalabsai/da-policyengine-dev/swagger-ui.html -- Health Check: https://huggingface.co/spaces/dalabsai/da-policyengine-dev/actuator/health - -## Environment - -- **Environment**: dev -- **Port**: 8080 -- **Java Version**: 21 -- **Framework**: Spring Boot - -## Deployment - -This service is automatically deployed via the DALab CI/CD pipeline. - -Last updated: 2025-06-16 23:40:00 +--- +title: da-policyengine (dev) +emoji: 🔧 +colorFrom: blue +colorTo: green +sdk: docker +app_port: 8080 +--- + +# da-policyengine - dev Environment + +This is the da-policyengine microservice deployed in the dev environment. + +## Features + +- RESTful API endpoints +- Health monitoring via Actuator +- JWT authentication integration +- PostgreSQL database connectivity + +## API Documentation + +Once deployed, API documentation will be available at: +- Swagger UI: https://huggingface.co/spaces/dalabsai/da-policyengine-dev/swagger-ui.html +- Health Check: https://huggingface.co/spaces/dalabsai/da-policyengine-dev/actuator/health + +## Environment + +- **Environment**: dev +- **Port**: 8080 +- **Java Version**: 21 +- **Framework**: Spring Boot + +## Deployment + +This service is automatically deployed via the DALab CI/CD pipeline. + +Last updated: Tue 06/17/2025 9:36:28.45 diff --git a/build.gradle.kts b/build.gradle.kts index abe68a46f3535e75c66f63920bdcf7274f82726e..e78e596f99387847a1fe48d6a5095f84aa364cf4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,16 +1,16 @@ -// da-policyengine inherits common configuration from parent build.gradle.kts -// This build file adds policy-engine-specific dependencies - -dependencies { - // Easy Rules engine for policy evaluation - implementation("org.jeasy:easy-rules-core:4.1.0") - implementation("org.jeasy:easy-rules-mvel:4.1.0") - - // Additional dependencies specific to da-policyengine - implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.1.1") -} - -// Configure main application class -configure { - mainClass.set("com.dalab.policyengine.DaPolicyEngineApplication") +// da-policyengine inherits common configuration from parent build.gradle.kts +// This build file adds policy-engine-specific dependencies + +dependencies { + // Easy Rules engine for policy evaluation + implementation("org.jeasy:easy-rules-core:4.1.0") + implementation("org.jeasy:easy-rules-mvel:4.1.0") + + // Additional dependencies specific to da-policyengine + implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.1.1") +} + +// Configure main application class +configure { + mainClass.set("com.dalab.policyengine.DaPolicyEngineApplication") } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..506706a049962aecc0330e1f4450c3b1216168dd --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000000000000000000000000000000000000..a4911a5d4cf8024d0733d9d8efb8d4449ffd4e20 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..5eed7ee8452842305a18a4eb967442683808226a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..41d57f9281daa4b5fadb429fdf42812fe40cfad5 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,16 @@ +rootProject.name = "microservices-dalab" + +// Include all microservices and the new protos project +include("da-protos") +include("da-discovery") +include("da-catalog") +include("da-policyengine") +include("da-reporting") +include("da-autolabel") +include("da-autoarchival") +include("da-autodelete") +include("da-autocompliance") +include("da-admin-service") + +// Potentially include other future services here +// include("da-admin-service") \ No newline at end of file diff --git a/src/main/docker/Dockerfile b/src/main/docker/Dockerfile index 4017cae7e25c3fb09d4d288e522bdaa44f30cf95..ec560088f65781e533cd76a4609ec99ca578e48f 100644 --- a/src/main/docker/Dockerfile +++ b/src/main/docker/Dockerfile @@ -1,23 +1,23 @@ -# Ultra-lean container using Google Distroless -# Expected final size: ~120-180MB (minimal base + JRE + JAR only) - -FROM gcr.io/distroless/java21-debian12:nonroot - -# Set working directory -WORKDIR /app - -# Copy JAR file -COPY build/libs/da-policyengine.jar app.jar - -# Expose standard Spring Boot port -EXPOSE 8080 - -# Run application (distroless has no shell, so use exec form) -ENTRYPOINT ["java", \ - "-XX:+UseContainerSupport", \ - "-XX:MaxRAMPercentage=75.0", \ - "-XX:+UseG1GC", \ - "-XX:+UseStringDeduplication", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.backgroundpreinitializer.ignore=true", \ - "-jar", "app.jar"] +# Ultra-lean container using Google Distroless +# Expected final size: ~120-180MB (minimal base + JRE + JAR only) + +FROM gcr.io/distroless/java21-debian12:nonroot + +# Set working directory +WORKDIR /app + +# Copy JAR file +COPY build/libs/da-policyengine.jar app.jar + +# Expose standard Spring Boot port +EXPOSE 8080 + +# Run application (distroless has no shell, so use exec form) +ENTRYPOINT ["java", \ + "-XX:+UseContainerSupport", \ + "-XX:MaxRAMPercentage=75.0", \ + "-XX:+UseG1GC", \ + "-XX:+UseStringDeduplication", \ + "-Djava.security.egd=file:/dev/./urandom", \ + "-Dspring.backgroundpreinitializer.ignore=true", \ + "-jar", "app.jar"] diff --git a/src/main/docker/Dockerfile.alpine-jlink b/src/main/docker/Dockerfile.alpine-jlink index ffb0b6cf87260a96fb83ac7a111d2fc905fc3e3b..de60eb418806d5f39b4054a71bd15006b1eb16b4 100644 --- a/src/main/docker/Dockerfile.alpine-jlink +++ b/src/main/docker/Dockerfile.alpine-jlink @@ -1,43 +1,43 @@ -# Ultra-minimal Alpine + Custom JRE -# Expected size: ~120-160MB - -# Stage 1: Create custom JRE with only needed modules -FROM eclipse-temurin:21-jdk-alpine as jre-builder -WORKDIR /app - -# Analyze JAR to find required modules -COPY build/libs/*.jar app.jar -RUN jdeps --ignore-missing-deps --print-module-deps app.jar > modules.txt - -# Create minimal JRE with only required modules -RUN jlink \ - --add-modules $(cat modules.txt),java.logging,java.xml,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \ - --strip-debug \ - --no-man-pages \ - --no-header-files \ - --compress=2 \ - --output /custom-jre - -# Stage 2: Production image -FROM alpine:3.19 -RUN apk add --no-cache tzdata && \ - addgroup -g 1001 -S appgroup && \ - adduser -u 1001 -S appuser -G appgroup - -# Copy custom JRE -COPY --from=jre-builder /custom-jre /opt/java -ENV JAVA_HOME=/opt/java -ENV PATH="$JAVA_HOME/bin:$PATH" - -WORKDIR /app -COPY build/libs/*.jar app.jar -RUN chown appuser:appgroup app.jar - -USER appuser -EXPOSE 8080 - -ENTRYPOINT ["java", \ - "-XX:+UseContainerSupport", \ - "-XX:MaxRAMPercentage=70.0", \ - "-XX:+UseG1GC", \ - "-jar", "app.jar"] +# Ultra-minimal Alpine + Custom JRE +# Expected size: ~120-160MB + +# Stage 1: Create custom JRE with only needed modules +FROM eclipse-temurin:21-jdk-alpine as jre-builder +WORKDIR /app + +# Analyze JAR to find required modules +COPY build/libs/*.jar app.jar +RUN jdeps --ignore-missing-deps --print-module-deps app.jar > modules.txt + +# Create minimal JRE with only required modules +RUN jlink \ + --add-modules $(cat modules.txt),java.logging,java.xml,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \ + --strip-debug \ + --no-man-pages \ + --no-header-files \ + --compress=2 \ + --output /custom-jre + +# Stage 2: Production image +FROM alpine:3.19 +RUN apk add --no-cache tzdata && \ + addgroup -g 1001 -S appgroup && \ + adduser -u 1001 -S appuser -G appgroup + +# Copy custom JRE +COPY --from=jre-builder /custom-jre /opt/java +ENV JAVA_HOME=/opt/java +ENV PATH="$JAVA_HOME/bin:$PATH" + +WORKDIR /app +COPY build/libs/*.jar app.jar +RUN chown appuser:appgroup app.jar + +USER appuser +EXPOSE 8080 + +ENTRYPOINT ["java", \ + "-XX:+UseContainerSupport", \ + "-XX:MaxRAMPercentage=70.0", \ + "-XX:+UseG1GC", \ + "-jar", "app.jar"] diff --git a/src/main/docker/Dockerfile.layered b/src/main/docker/Dockerfile.layered index 59231523ddffaa9aa5ce1f62b1df71600bae6d7f..31b303c403d7f8f5352f63836b649337cd963ea3 100644 --- a/src/main/docker/Dockerfile.layered +++ b/src/main/docker/Dockerfile.layered @@ -1,34 +1,34 @@ -# Ultra-optimized layered build using Distroless -# Expected size: ~180-220MB with better caching - -FROM gcr.io/distroless/java21-debian12:nonroot as base - -# Stage 1: Extract JAR layers for optimal caching -FROM eclipse-temurin:21-jdk-alpine as extractor -WORKDIR /app -COPY build/libs/*.jar app.jar -RUN java -Djarmode=layertools -jar app.jar extract - -# Stage 2: Production image with extracted layers -FROM base -WORKDIR /app - -# Copy layers in dependency order (best caching) -COPY --from=extractor /app/dependencies/ ./ -COPY --from=extractor /app/spring-boot-loader/ ./ -COPY --from=extractor /app/snapshot-dependencies/ ./ -COPY --from=extractor /app/application/ ./ - -EXPOSE 8080 - -# Optimized JVM settings for micro-containers -ENTRYPOINT ["java", \ - "-XX:+UseContainerSupport", \ - "-XX:MaxRAMPercentage=70.0", \ - "-XX:+UseG1GC", \ - "-XX:+UseStringDeduplication", \ - "-XX:+CompactStrings", \ - "-Xshare:on", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.backgroundpreinitializer.ignore=true", \ - "org.springframework.boot.loader.JarLauncher"] +# Ultra-optimized layered build using Distroless +# Expected size: ~180-220MB with better caching + +FROM gcr.io/distroless/java21-debian12:nonroot as base + +# Stage 1: Extract JAR layers for optimal caching +FROM eclipse-temurin:21-jdk-alpine as extractor +WORKDIR /app +COPY build/libs/*.jar app.jar +RUN java -Djarmode=layertools -jar app.jar extract + +# Stage 2: Production image with extracted layers +FROM base +WORKDIR /app + +# Copy layers in dependency order (best caching) +COPY --from=extractor /app/dependencies/ ./ +COPY --from=extractor /app/spring-boot-loader/ ./ +COPY --from=extractor /app/snapshot-dependencies/ ./ +COPY --from=extractor /app/application/ ./ + +EXPOSE 8080 + +# Optimized JVM settings for micro-containers +ENTRYPOINT ["java", \ + "-XX:+UseContainerSupport", \ + "-XX:MaxRAMPercentage=70.0", \ + "-XX:+UseG1GC", \ + "-XX:+UseStringDeduplication", \ + "-XX:+CompactStrings", \ + "-Xshare:on", \ + "-Djava.security.egd=file:/dev/./urandom", \ + "-Dspring.backgroundpreinitializer.ignore=true", \ + "org.springframework.boot.loader.JarLauncher"] diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native index 5135bb6fe05c48308f7b3f756fec66c7525aa6a8..f6b97e83134de0d876a320bfc1890cfd1adeb845 100644 --- a/src/main/docker/Dockerfile.native +++ b/src/main/docker/Dockerfile.native @@ -1,20 +1,20 @@ -# GraalVM Native Image - Ultra-fast startup, tiny size -# Expected size: ~50-80MB, startup <100ms -# Note: Requires native compilation support in Spring Boot - -# Stage 1: Native compilation -FROM ghcr.io/graalvm/graalvm-ce:ol9-java21 as native-builder -WORKDIR /app - -# Install native-image -RUN gu install native-image - -# Copy source and build native executable -COPY . . -RUN ./gradlew nativeCompile - -# Stage 2: Minimal runtime -FROM scratch -COPY --from=native-builder /app/build/native/nativeCompile/app /app -EXPOSE 8080 -ENTRYPOINT ["/app"] +# GraalVM Native Image - Ultra-fast startup, tiny size +# Expected size: ~50-80MB, startup <100ms +# Note: Requires native compilation support in Spring Boot + +# Stage 1: Native compilation +FROM ghcr.io/graalvm/graalvm-ce:ol9-java21 as native-builder +WORKDIR /app + +# Install native-image +RUN gu install native-image + +# Copy source and build native executable +COPY . . +RUN ./gradlew nativeCompile + +# Stage 2: Minimal runtime +FROM scratch +COPY --from=native-builder /app/build/native/nativeCompile/app /app +EXPOSE 8080 +ENTRYPOINT ["/app"] diff --git a/src/main/java/com/dalab/policyengine/DaPolicyEngineApplication.java b/src/main/java/com/dalab/policyengine/DaPolicyEngineApplication.java index 6f5ec7bda0ae7028210f8015479a2d454c0106d2..b1d451ec54ebe854668f2acb7f9d7974c307e75c 100644 --- a/src/main/java/com/dalab/policyengine/DaPolicyEngineApplication.java +++ b/src/main/java/com/dalab/policyengine/DaPolicyEngineApplication.java @@ -1,37 +1,37 @@ -package com.dalab.policyengine; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.kafka.annotation.EnableKafka; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.annotation.EnableScheduling; - -/** - * Main application class for DALab Policy Engine Service. - * - * This service handles: - * - Policy management and evaluation - * - Easy Rules engine integration - * - Kafka event processing for asset changes - * - Policy action execution and notifications - * - * @author DALab Development Team - * @since 1.0.0 - */ -@SpringBootApplication(scanBasePackages = { - "com.dalab.policyengine", - "com.dalab.discovery.common.security" // Include common security utils -}) -@EnableJpaRepositories -@EnableFeignClients -@EnableKafka -@EnableAsync -@EnableScheduling -public class DaPolicyEngineApplication { - - public static void main(String[] args) { - SpringApplication.run(DaPolicyEngineApplication.class, args); - } +package com.dalab.policyengine; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * Main application class for DALab Policy Engine Service. + * + * This service handles: + * - Policy management and evaluation + * - Easy Rules engine integration + * - Kafka event processing for asset changes + * - Policy action execution and notifications + * + * @author DALab Development Team + * @since 1.0.0 + */ +@SpringBootApplication(scanBasePackages = { + "com.dalab.policyengine", + "com.dalab.discovery.common.security" // Include common security utils +}) +@EnableJpaRepositories +@EnableFeignClients +@EnableKafka +@EnableAsync +@EnableScheduling +public class DaPolicyEngineApplication { + + public static void main(String[] args) { + SpringApplication.run(DaPolicyEngineApplication.class, args); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/common/ConflictException.java b/src/main/java/com/dalab/policyengine/common/ConflictException.java index 1c19819c1f6a5f9fb0d4fba90c0e74b6263d82f4..048e195e7213e719204537fb8997d0f81d985227 100644 --- a/src/main/java/com/dalab/policyengine/common/ConflictException.java +++ b/src/main/java/com/dalab/policyengine/common/ConflictException.java @@ -1,11 +1,11 @@ -package com.dalab.policyengine.common; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.CONFLICT) -public class ConflictException extends RuntimeException { - public ConflictException(String message) { - super(message); - } +package com.dalab.policyengine.common; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.CONFLICT) +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/common/ResourceNotFoundException.java b/src/main/java/com/dalab/policyengine/common/ResourceNotFoundException.java index 139732259f9cc327434dacc7ebf720231ce4d5c6..67a35ea168e549ae6b6d1ca281cb32b3c0da118f 100644 --- a/src/main/java/com/dalab/policyengine/common/ResourceNotFoundException.java +++ b/src/main/java/com/dalab/policyengine/common/ResourceNotFoundException.java @@ -1,15 +1,15 @@ -package com.dalab.policyengine.common; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) -public class ResourceNotFoundException extends RuntimeException { - public ResourceNotFoundException(String resourceName, String fieldName, String fieldValue) { - super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); - } - - public ResourceNotFoundException(String message) { - super(message); - } +package com.dalab.policyengine.common; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String resourceName, String fieldName, String fieldValue) { + super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); + } + + public ResourceNotFoundException(String message) { + super(message); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/config/OpenAPIConfiguration.java b/src/main/java/com/dalab/policyengine/config/OpenAPIConfiguration.java index f0757bdf0327c602f708b51791c7988ae6e5d41c..a521d880cf6f83a873d2533ff61d9efb1510db4c 100644 --- a/src/main/java/com/dalab/policyengine/config/OpenAPIConfiguration.java +++ b/src/main/java/com/dalab/policyengine/config/OpenAPIConfiguration.java @@ -1,27 +1,27 @@ -package com.dalab.policyengine.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.info.License; - -@Configuration -public class OpenAPIConfiguration { - - @Bean - public OpenAPI customOpenAPI( - @Value("${spring.application.name}") String appName, - @Value("${spring.application.description:DALab Policy Engine Microservice}") String appDescription, - @Value("${spring.application.version:v0.0.1}") String appVersion) { - return new OpenAPI() - .info(new Info() - .title(appName) - .version(appVersion) - .description(appDescription) - .termsOfService("http://swagger.io/terms/") // Replace with actual terms - .license(new License().name("Apache 2.0").url("http://springdoc.org"))); // Replace with actual license - } +package com.dalab.policyengine.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; + +@Configuration +public class OpenAPIConfiguration { + + @Bean + public OpenAPI customOpenAPI( + @Value("${spring.application.name}") String appName, + @Value("${spring.application.description:DALab Policy Engine Microservice}") String appDescription, + @Value("${spring.application.version:v0.0.1}") String appVersion) { + return new OpenAPI() + .info(new Info() + .title(appName) + .version(appVersion) + .description(appDescription) + .termsOfService("http://swagger.io/terms/") // Replace with actual terms + .license(new License().name("Apache 2.0").url("http://springdoc.org"))); // Replace with actual license + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/config/SecurityConfiguration.java b/src/main/java/com/dalab/policyengine/config/SecurityConfiguration.java index 4634dd85159b1cf91ee8619ebfd32928013d6d2b..b1bb4b3b0438eda16358ced09c26bd3d1c13bee2 100644 --- a/src/main/java/com/dalab/policyengine/config/SecurityConfiguration.java +++ b/src/main/java/com/dalab/policyengine/config/SecurityConfiguration.java @@ -1,44 +1,44 @@ -package com.dalab.policyengine.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.web.SecurityFilterChain; - -@Configuration -@EnableWebSecurity -@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) -public class SecurityConfiguration { - - @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") - private String issuerUri; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .csrf(csrf -> csrf.disable()) // Typically disable CSRF for stateless REST APIs - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(authz -> authz - // Define public endpoints if any (e.g., actuator/health, swagger-ui) - // .requestMatchers("/public/**").permitAll() - .anyRequest().authenticated() // All other requests require authentication - ) - .oauth2ResourceServer(oauth2 -> oauth2 - .jwt(jwt -> jwt.decoder(jwtDecoder())) - ); - return http.build(); - } - - @Bean - public JwtDecoder jwtDecoder() { - // NimbusJwtDecoder automatically fetches the JWK Set URI from the issuer URI - // (e.g., ISSUER_URI/.well-known/openid-configuration or ISSUER_URI/protocol/openid-connect/certs) - return NimbusJwtDecoder.withIssuerLocation(issuerUri).build(); - } +package com.dalab.policyengine.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfiguration { + + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") + private String issuerUri; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) // Typically disable CSRF for stateless REST APIs + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authz -> authz + // Define public endpoints if any (e.g., actuator/health, swagger-ui) + // .requestMatchers("/public/**").permitAll() + .anyRequest().authenticated() // All other requests require authentication + ) + .oauth2ResourceServer(oauth2 -> oauth2 + .jwt(jwt -> jwt.decoder(jwtDecoder())) + ); + return http.build(); + } + + @Bean + public JwtDecoder jwtDecoder() { + // NimbusJwtDecoder automatically fetches the JWK Set URI from the issuer URI + // (e.g., ISSUER_URI/.well-known/openid-configuration or ISSUER_URI/protocol/openid-connect/certs) + return NimbusJwtDecoder.withIssuerLocation(issuerUri).build(); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/EventAnalyticsDTO.java b/src/main/java/com/dalab/policyengine/dto/EventAnalyticsDTO.java index 08ee1b0bcff86b56b309f84c736f5ecb7b069510..bc6e45c755cc318c6baa95e8cbe62ff9d7d2cf40 100644 --- a/src/main/java/com/dalab/policyengine/dto/EventAnalyticsDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/EventAnalyticsDTO.java @@ -1,349 +1,349 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.List; -import java.util.Map; - -import com.dalab.policyengine.model.EventSeverity; -import com.dalab.policyengine.model.EventType; - -/** - * DTO for Event Center analytics and metrics dashboard. - */ -public class EventAnalyticsDTO { - - // Summary metrics - private Long totalEventsToday; - private Long totalEventsThisWeek; - private Long totalEventsThisMonth; - private Long totalActiveSubscriptions; - private Long totalUsersWithSubscriptions; - - // Event distribution by type - private List eventsByType; - - // Event distribution by severity - private List eventsBySeverity; - - // Event distribution by source service - private List eventsBySourceService; - - // Top active subscriptions - private List topActiveSubscriptions; - - // Recent event trends (hourly for last 24 hours) - private List recentTrends; - - // Response time metrics - private ResponseTimeMetrics responseMetrics; - - // Alert statistics - private AlertStatistics alertStats; - - // Time range for this analytics report - private Instant fromTime; - private Instant toTime; - private Instant generatedAt; - - // Constructors - public EventAnalyticsDTO() { - this.generatedAt = Instant.now(); - } - - public EventAnalyticsDTO(Instant fromTime, Instant toTime) { - this.fromTime = fromTime; - this.toTime = toTime; - this.generatedAt = Instant.now(); - } - - // Getters and Setters - - public Long getTotalEventsToday() { - return totalEventsToday; - } - - public void setTotalEventsToday(Long totalEventsToday) { - this.totalEventsToday = totalEventsToday; - } - - public Long getTotalEventsThisWeek() { - return totalEventsThisWeek; - } - - public void setTotalEventsThisWeek(Long totalEventsThisWeek) { - this.totalEventsThisWeek = totalEventsThisWeek; - } - - public Long getTotalEventsThisMonth() { - return totalEventsThisMonth; - } - - public void setTotalEventsThisMonth(Long totalEventsThisMonth) { - this.totalEventsThisMonth = totalEventsThisMonth; - } - - public Long getTotalActiveSubscriptions() { - return totalActiveSubscriptions; - } - - public void setTotalActiveSubscriptions(Long totalActiveSubscriptions) { - this.totalActiveSubscriptions = totalActiveSubscriptions; - } - - public Long getTotalUsersWithSubscriptions() { - return totalUsersWithSubscriptions; - } - - public void setTotalUsersWithSubscriptions(Long totalUsersWithSubscriptions) { - this.totalUsersWithSubscriptions = totalUsersWithSubscriptions; - } - - public List getEventsByType() { - return eventsByType; - } - - public void setEventsByType(List eventsByType) { - this.eventsByType = eventsByType; - } - - public List getEventsBySeverity() { - return eventsBySeverity; - } - - public void setEventsBySeverity(List eventsBySeverity) { - this.eventsBySeverity = eventsBySeverity; - } - - public List getEventsBySourceService() { - return eventsBySourceService; - } - - public void setEventsBySourceService(List eventsBySourceService) { - this.eventsBySourceService = eventsBySourceService; - } - - public List getTopActiveSubscriptions() { - return topActiveSubscriptions; - } - - public void setTopActiveSubscriptions(List topActiveSubscriptions) { - this.topActiveSubscriptions = topActiveSubscriptions; - } - - public List getRecentTrends() { - return recentTrends; - } - - public void setRecentTrends(List recentTrends) { - this.recentTrends = recentTrends; - } - - public ResponseTimeMetrics getResponseMetrics() { - return responseMetrics; - } - - public void setResponseMetrics(ResponseTimeMetrics responseMetrics) { - this.responseMetrics = responseMetrics; - } - - public AlertStatistics getAlertStats() { - return alertStats; - } - - public void setAlertStats(AlertStatistics alertStats) { - this.alertStats = alertStats; - } - - public Instant getFromTime() { - return fromTime; - } - - public void setFromTime(Instant fromTime) { - this.fromTime = fromTime; - } - - public Instant getToTime() { - return toTime; - } - - public void setToTime(Instant toTime) { - this.toTime = toTime; - } - - public Instant getGeneratedAt() { - return generatedAt; - } - - public void setGeneratedAt(Instant generatedAt) { - this.generatedAt = generatedAt; - } - - @Override - public String toString() { - return "EventAnalyticsDTO{" + - "totalEventsToday=" + totalEventsToday + - ", totalEventsThisWeek=" + totalEventsThisWeek + - ", totalEventsThisMonth=" + totalEventsThisMonth + - ", totalActiveSubscriptions=" + totalActiveSubscriptions + - ", fromTime=" + fromTime + - ", toTime=" + toTime + - ", generatedAt=" + generatedAt + - '}'; - } - - // Nested classes for analytics data structures - - public static class EventTypeCount { - private EventType eventType; - private Long count; - private Double percentage; - - public EventTypeCount() {} - - public EventTypeCount(EventType eventType, Long count) { - this.eventType = eventType; - this.count = count; - } - - public EventType getEventType() { return eventType; } - public void setEventType(EventType eventType) { this.eventType = eventType; } - public Long getCount() { return count; } - public void setCount(Long count) { this.count = count; } - public Double getPercentage() { return percentage; } - public void setPercentage(Double percentage) { this.percentage = percentage; } - } - - public static class EventSeverityCount { - private EventSeverity severity; - private Long count; - private Double percentage; - - public EventSeverityCount() {} - - public EventSeverityCount(EventSeverity severity, Long count) { - this.severity = severity; - this.count = count; - } - - public EventSeverity getSeverity() { return severity; } - public void setSeverity(EventSeverity severity) { this.severity = severity; } - public Long getCount() { return count; } - public void setCount(Long count) { this.count = count; } - public Double getPercentage() { return percentage; } - public void setPercentage(Double percentage) { this.percentage = percentage; } - } - - public static class SourceServiceCount { - private String sourceService; - private Long count; - private Double percentage; - - public SourceServiceCount() {} - - public SourceServiceCount(String sourceService, Long count) { - this.sourceService = sourceService; - this.count = count; - } - - public String getSourceService() { return sourceService; } - public void setSourceService(String sourceService) { this.sourceService = sourceService; } - public Long getCount() { return count; } - public void setCount(Long count) { this.count = count; } - public Double getPercentage() { return percentage; } - public void setPercentage(Double percentage) { this.percentage = percentage; } - } - - public static class TopSubscription { - private String subscriptionName; - private Long eventsMatched; - private Long notificationsSent; - private String ownerName; - private Instant lastActivity; - - public TopSubscription() {} - - public TopSubscription(String subscriptionName, Long eventsMatched) { - this.subscriptionName = subscriptionName; - this.eventsMatched = eventsMatched; - } - - public String getSubscriptionName() { return subscriptionName; } - public void setSubscriptionName(String subscriptionName) { this.subscriptionName = subscriptionName; } - public Long getEventsMatched() { return eventsMatched; } - public void setEventsMatched(Long eventsMatched) { this.eventsMatched = eventsMatched; } - public Long getNotificationsSent() { return notificationsSent; } - public void setNotificationsSent(Long notificationsSent) { this.notificationsSent = notificationsSent; } - public String getOwnerName() { return ownerName; } - public void setOwnerName(String ownerName) { this.ownerName = ownerName; } - public Instant getLastActivity() { return lastActivity; } - public void setLastActivity(Instant lastActivity) { this.lastActivity = lastActivity; } - } - - public static class EventTrendPoint { - private Instant timestamp; - private Long eventCount; - private Long criticalCount; - private Long highCount; - private Long mediumCount; - private Long lowCount; - - public EventTrendPoint() {} - - public EventTrendPoint(Instant timestamp, Long eventCount) { - this.timestamp = timestamp; - this.eventCount = eventCount; - } - - public Instant getTimestamp() { return timestamp; } - public void setTimestamp(Instant timestamp) { this.timestamp = timestamp; } - public Long getEventCount() { return eventCount; } - public void setEventCount(Long eventCount) { this.eventCount = eventCount; } - public Long getCriticalCount() { return criticalCount; } - public void setCriticalCount(Long criticalCount) { this.criticalCount = criticalCount; } - public Long getHighCount() { return highCount; } - public void setHighCount(Long highCount) { this.highCount = highCount; } - public Long getMediumCount() { return mediumCount; } - public void setMediumCount(Long mediumCount) { this.mediumCount = mediumCount; } - public Long getLowCount() { return lowCount; } - public void setLowCount(Long lowCount) { this.lowCount = lowCount; } - } - - public static class ResponseTimeMetrics { - private Double averageProcessingTimeMs; - private Double averageNotificationTimeMs; - private Long totalProcessedEvents; - private Long failedNotifications; - private Double successRate; - - public ResponseTimeMetrics() {} - - public Double getAverageProcessingTimeMs() { return averageProcessingTimeMs; } - public void setAverageProcessingTimeMs(Double averageProcessingTimeMs) { this.averageProcessingTimeMs = averageProcessingTimeMs; } - public Double getAverageNotificationTimeMs() { return averageNotificationTimeMs; } - public void setAverageNotificationTimeMs(Double averageNotificationTimeMs) { this.averageNotificationTimeMs = averageNotificationTimeMs; } - public Long getTotalProcessedEvents() { return totalProcessedEvents; } - public void setTotalProcessedEvents(Long totalProcessedEvents) { this.totalProcessedEvents = totalProcessedEvents; } - public Long getFailedNotifications() { return failedNotifications; } - public void setFailedNotifications(Long failedNotifications) { this.failedNotifications = failedNotifications; } - public Double getSuccessRate() { return successRate; } - public void setSuccessRate(Double successRate) { this.successRate = successRate; } - } - - public static class AlertStatistics { - private Long totalAlertsTriggered; - private Long totalNotificationsSent; - private Long totalActionsExecuted; - private Map alertsByChannel; // email, slack, webhook, etc. - - public AlertStatistics() {} - - public Long getTotalAlertsTriggered() { return totalAlertsTriggered; } - public void setTotalAlertsTriggered(Long totalAlertsTriggered) { this.totalAlertsTriggered = totalAlertsTriggered; } - public Long getTotalNotificationsSent() { return totalNotificationsSent; } - public void setTotalNotificationsSent(Long totalNotificationsSent) { this.totalNotificationsSent = totalNotificationsSent; } - public Long getTotalActionsExecuted() { return totalActionsExecuted; } - public void setTotalActionsExecuted(Long totalActionsExecuted) { this.totalActionsExecuted = totalActionsExecuted; } - public Map getAlertsByChannel() { return alertsByChannel; } - public void setAlertsByChannel(Map alertsByChannel) { this.alertsByChannel = alertsByChannel; } - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import com.dalab.policyengine.model.EventSeverity; +import com.dalab.policyengine.model.EventType; + +/** + * DTO for Event Center analytics and metrics dashboard. + */ +public class EventAnalyticsDTO { + + // Summary metrics + private Long totalEventsToday; + private Long totalEventsThisWeek; + private Long totalEventsThisMonth; + private Long totalActiveSubscriptions; + private Long totalUsersWithSubscriptions; + + // Event distribution by type + private List eventsByType; + + // Event distribution by severity + private List eventsBySeverity; + + // Event distribution by source service + private List eventsBySourceService; + + // Top active subscriptions + private List topActiveSubscriptions; + + // Recent event trends (hourly for last 24 hours) + private List recentTrends; + + // Response time metrics + private ResponseTimeMetrics responseMetrics; + + // Alert statistics + private AlertStatistics alertStats; + + // Time range for this analytics report + private Instant fromTime; + private Instant toTime; + private Instant generatedAt; + + // Constructors + public EventAnalyticsDTO() { + this.generatedAt = Instant.now(); + } + + public EventAnalyticsDTO(Instant fromTime, Instant toTime) { + this.fromTime = fromTime; + this.toTime = toTime; + this.generatedAt = Instant.now(); + } + + // Getters and Setters + + public Long getTotalEventsToday() { + return totalEventsToday; + } + + public void setTotalEventsToday(Long totalEventsToday) { + this.totalEventsToday = totalEventsToday; + } + + public Long getTotalEventsThisWeek() { + return totalEventsThisWeek; + } + + public void setTotalEventsThisWeek(Long totalEventsThisWeek) { + this.totalEventsThisWeek = totalEventsThisWeek; + } + + public Long getTotalEventsThisMonth() { + return totalEventsThisMonth; + } + + public void setTotalEventsThisMonth(Long totalEventsThisMonth) { + this.totalEventsThisMonth = totalEventsThisMonth; + } + + public Long getTotalActiveSubscriptions() { + return totalActiveSubscriptions; + } + + public void setTotalActiveSubscriptions(Long totalActiveSubscriptions) { + this.totalActiveSubscriptions = totalActiveSubscriptions; + } + + public Long getTotalUsersWithSubscriptions() { + return totalUsersWithSubscriptions; + } + + public void setTotalUsersWithSubscriptions(Long totalUsersWithSubscriptions) { + this.totalUsersWithSubscriptions = totalUsersWithSubscriptions; + } + + public List getEventsByType() { + return eventsByType; + } + + public void setEventsByType(List eventsByType) { + this.eventsByType = eventsByType; + } + + public List getEventsBySeverity() { + return eventsBySeverity; + } + + public void setEventsBySeverity(List eventsBySeverity) { + this.eventsBySeverity = eventsBySeverity; + } + + public List getEventsBySourceService() { + return eventsBySourceService; + } + + public void setEventsBySourceService(List eventsBySourceService) { + this.eventsBySourceService = eventsBySourceService; + } + + public List getTopActiveSubscriptions() { + return topActiveSubscriptions; + } + + public void setTopActiveSubscriptions(List topActiveSubscriptions) { + this.topActiveSubscriptions = topActiveSubscriptions; + } + + public List getRecentTrends() { + return recentTrends; + } + + public void setRecentTrends(List recentTrends) { + this.recentTrends = recentTrends; + } + + public ResponseTimeMetrics getResponseMetrics() { + return responseMetrics; + } + + public void setResponseMetrics(ResponseTimeMetrics responseMetrics) { + this.responseMetrics = responseMetrics; + } + + public AlertStatistics getAlertStats() { + return alertStats; + } + + public void setAlertStats(AlertStatistics alertStats) { + this.alertStats = alertStats; + } + + public Instant getFromTime() { + return fromTime; + } + + public void setFromTime(Instant fromTime) { + this.fromTime = fromTime; + } + + public Instant getToTime() { + return toTime; + } + + public void setToTime(Instant toTime) { + this.toTime = toTime; + } + + public Instant getGeneratedAt() { + return generatedAt; + } + + public void setGeneratedAt(Instant generatedAt) { + this.generatedAt = generatedAt; + } + + @Override + public String toString() { + return "EventAnalyticsDTO{" + + "totalEventsToday=" + totalEventsToday + + ", totalEventsThisWeek=" + totalEventsThisWeek + + ", totalEventsThisMonth=" + totalEventsThisMonth + + ", totalActiveSubscriptions=" + totalActiveSubscriptions + + ", fromTime=" + fromTime + + ", toTime=" + toTime + + ", generatedAt=" + generatedAt + + '}'; + } + + // Nested classes for analytics data structures + + public static class EventTypeCount { + private EventType eventType; + private Long count; + private Double percentage; + + public EventTypeCount() {} + + public EventTypeCount(EventType eventType, Long count) { + this.eventType = eventType; + this.count = count; + } + + public EventType getEventType() { return eventType; } + public void setEventType(EventType eventType) { this.eventType = eventType; } + public Long getCount() { return count; } + public void setCount(Long count) { this.count = count; } + public Double getPercentage() { return percentage; } + public void setPercentage(Double percentage) { this.percentage = percentage; } + } + + public static class EventSeverityCount { + private EventSeverity severity; + private Long count; + private Double percentage; + + public EventSeverityCount() {} + + public EventSeverityCount(EventSeverity severity, Long count) { + this.severity = severity; + this.count = count; + } + + public EventSeverity getSeverity() { return severity; } + public void setSeverity(EventSeverity severity) { this.severity = severity; } + public Long getCount() { return count; } + public void setCount(Long count) { this.count = count; } + public Double getPercentage() { return percentage; } + public void setPercentage(Double percentage) { this.percentage = percentage; } + } + + public static class SourceServiceCount { + private String sourceService; + private Long count; + private Double percentage; + + public SourceServiceCount() {} + + public SourceServiceCount(String sourceService, Long count) { + this.sourceService = sourceService; + this.count = count; + } + + public String getSourceService() { return sourceService; } + public void setSourceService(String sourceService) { this.sourceService = sourceService; } + public Long getCount() { return count; } + public void setCount(Long count) { this.count = count; } + public Double getPercentage() { return percentage; } + public void setPercentage(Double percentage) { this.percentage = percentage; } + } + + public static class TopSubscription { + private String subscriptionName; + private Long eventsMatched; + private Long notificationsSent; + private String ownerName; + private Instant lastActivity; + + public TopSubscription() {} + + public TopSubscription(String subscriptionName, Long eventsMatched) { + this.subscriptionName = subscriptionName; + this.eventsMatched = eventsMatched; + } + + public String getSubscriptionName() { return subscriptionName; } + public void setSubscriptionName(String subscriptionName) { this.subscriptionName = subscriptionName; } + public Long getEventsMatched() { return eventsMatched; } + public void setEventsMatched(Long eventsMatched) { this.eventsMatched = eventsMatched; } + public Long getNotificationsSent() { return notificationsSent; } + public void setNotificationsSent(Long notificationsSent) { this.notificationsSent = notificationsSent; } + public String getOwnerName() { return ownerName; } + public void setOwnerName(String ownerName) { this.ownerName = ownerName; } + public Instant getLastActivity() { return lastActivity; } + public void setLastActivity(Instant lastActivity) { this.lastActivity = lastActivity; } + } + + public static class EventTrendPoint { + private Instant timestamp; + private Long eventCount; + private Long criticalCount; + private Long highCount; + private Long mediumCount; + private Long lowCount; + + public EventTrendPoint() {} + + public EventTrendPoint(Instant timestamp, Long eventCount) { + this.timestamp = timestamp; + this.eventCount = eventCount; + } + + public Instant getTimestamp() { return timestamp; } + public void setTimestamp(Instant timestamp) { this.timestamp = timestamp; } + public Long getEventCount() { return eventCount; } + public void setEventCount(Long eventCount) { this.eventCount = eventCount; } + public Long getCriticalCount() { return criticalCount; } + public void setCriticalCount(Long criticalCount) { this.criticalCount = criticalCount; } + public Long getHighCount() { return highCount; } + public void setHighCount(Long highCount) { this.highCount = highCount; } + public Long getMediumCount() { return mediumCount; } + public void setMediumCount(Long mediumCount) { this.mediumCount = mediumCount; } + public Long getLowCount() { return lowCount; } + public void setLowCount(Long lowCount) { this.lowCount = lowCount; } + } + + public static class ResponseTimeMetrics { + private Double averageProcessingTimeMs; + private Double averageNotificationTimeMs; + private Long totalProcessedEvents; + private Long failedNotifications; + private Double successRate; + + public ResponseTimeMetrics() {} + + public Double getAverageProcessingTimeMs() { return averageProcessingTimeMs; } + public void setAverageProcessingTimeMs(Double averageProcessingTimeMs) { this.averageProcessingTimeMs = averageProcessingTimeMs; } + public Double getAverageNotificationTimeMs() { return averageNotificationTimeMs; } + public void setAverageNotificationTimeMs(Double averageNotificationTimeMs) { this.averageNotificationTimeMs = averageNotificationTimeMs; } + public Long getTotalProcessedEvents() { return totalProcessedEvents; } + public void setTotalProcessedEvents(Long totalProcessedEvents) { this.totalProcessedEvents = totalProcessedEvents; } + public Long getFailedNotifications() { return failedNotifications; } + public void setFailedNotifications(Long failedNotifications) { this.failedNotifications = failedNotifications; } + public Double getSuccessRate() { return successRate; } + public void setSuccessRate(Double successRate) { this.successRate = successRate; } + } + + public static class AlertStatistics { + private Long totalAlertsTriggered; + private Long totalNotificationsSent; + private Long totalActionsExecuted; + private Map alertsByChannel; // email, slack, webhook, etc. + + public AlertStatistics() {} + + public Long getTotalAlertsTriggered() { return totalAlertsTriggered; } + public void setTotalAlertsTriggered(Long totalAlertsTriggered) { this.totalAlertsTriggered = totalAlertsTriggered; } + public Long getTotalNotificationsSent() { return totalNotificationsSent; } + public void setTotalNotificationsSent(Long totalNotificationsSent) { this.totalNotificationsSent = totalNotificationsSent; } + public Long getTotalActionsExecuted() { return totalActionsExecuted; } + public void setTotalActionsExecuted(Long totalActionsExecuted) { this.totalActionsExecuted = totalActionsExecuted; } + public Map getAlertsByChannel() { return alertsByChannel; } + public void setAlertsByChannel(Map alertsByChannel) { this.alertsByChannel = alertsByChannel; } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/EventStreamDTO.java b/src/main/java/com/dalab/policyengine/dto/EventStreamDTO.java index dc3d1b903dc39a4d42b556ee84fa8293f36f825f..b82233e53dbb81f85a3ef72d62f05439cd788546 100644 --- a/src/main/java/com/dalab/policyengine/dto/EventStreamDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/EventStreamDTO.java @@ -1,216 +1,216 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -import com.dalab.policyengine.model.EventSeverity; -import com.dalab.policyengine.model.EventType; - -/** - * DTO for streaming events in real-time to the Event Center UI. - */ -public class EventStreamDTO { - - private UUID eventId; - private EventType eventType; - private EventSeverity severity; - private String sourceService; - private String title; - private String description; - private UUID assetId; - private String assetName; - private UUID policyId; - private String policyName; - private Map eventData; - private Map metadata; - private Instant timestamp; - private UUID userId; - private String userAction; - - // Event processing information - private Boolean isProcessed; - private Integer matchingSubscriptionsCount; - private Boolean notificationSent; - private Boolean actionTriggered; - - // Constructors - public EventStreamDTO() {} - - public EventStreamDTO(UUID eventId, EventType eventType, EventSeverity severity, String sourceService) { - this.eventId = eventId; - this.eventType = eventType; - this.severity = severity; - this.sourceService = sourceService; - this.timestamp = Instant.now(); - } - - // Getters and Setters - - public UUID getEventId() { - return eventId; - } - - public void setEventId(UUID eventId) { - this.eventId = eventId; - } - - public EventType getEventType() { - return eventType; - } - - public void setEventType(EventType eventType) { - this.eventType = eventType; - } - - public EventSeverity getSeverity() { - return severity; - } - - public void setSeverity(EventSeverity severity) { - this.severity = severity; - } - - public String getSourceService() { - return sourceService; - } - - public void setSourceService(String sourceService) { - this.sourceService = sourceService; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public UUID getAssetId() { - return assetId; - } - - public void setAssetId(UUID assetId) { - this.assetId = assetId; - } - - public String getAssetName() { - return assetName; - } - - public void setAssetName(String assetName) { - this.assetName = assetName; - } - - public UUID getPolicyId() { - return policyId; - } - - public void setPolicyId(UUID policyId) { - this.policyId = policyId; - } - - public String getPolicyName() { - return policyName; - } - - public void setPolicyName(String policyName) { - this.policyName = policyName; - } - - public Map getEventData() { - return eventData; - } - - public void setEventData(Map eventData) { - this.eventData = eventData; - } - - public Map getMetadata() { - return metadata; - } - - public void setMetadata(Map metadata) { - this.metadata = metadata; - } - - public Instant getTimestamp() { - return timestamp; - } - - public void setTimestamp(Instant timestamp) { - this.timestamp = timestamp; - } - - public UUID getUserId() { - return userId; - } - - public void setUserId(UUID userId) { - this.userId = userId; - } - - public String getUserAction() { - return userAction; - } - - public void setUserAction(String userAction) { - this.userAction = userAction; - } - - public Boolean getIsProcessed() { - return isProcessed; - } - - public void setIsProcessed(Boolean isProcessed) { - this.isProcessed = isProcessed; - } - - public Integer getMatchingSubscriptionsCount() { - return matchingSubscriptionsCount; - } - - public void setMatchingSubscriptionsCount(Integer matchingSubscriptionsCount) { - this.matchingSubscriptionsCount = matchingSubscriptionsCount; - } - - public Boolean getNotificationSent() { - return notificationSent; - } - - public void setNotificationSent(Boolean notificationSent) { - this.notificationSent = notificationSent; - } - - public Boolean getActionTriggered() { - return actionTriggered; - } - - public void setActionTriggered(Boolean actionTriggered) { - this.actionTriggered = actionTriggered; - } - - @Override - public String toString() { - return "EventStreamDTO{" + - "eventId=" + eventId + - ", eventType=" + eventType + - ", severity=" + severity + - ", sourceService='" + sourceService + '\'' + - ", title='" + title + '\'' + - ", assetId=" + assetId + - ", policyId=" + policyId + - ", timestamp=" + timestamp + - ", isProcessed=" + isProcessed + - '}'; - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import com.dalab.policyengine.model.EventSeverity; +import com.dalab.policyengine.model.EventType; + +/** + * DTO for streaming events in real-time to the Event Center UI. + */ +public class EventStreamDTO { + + private UUID eventId; + private EventType eventType; + private EventSeverity severity; + private String sourceService; + private String title; + private String description; + private UUID assetId; + private String assetName; + private UUID policyId; + private String policyName; + private Map eventData; + private Map metadata; + private Instant timestamp; + private UUID userId; + private String userAction; + + // Event processing information + private Boolean isProcessed; + private Integer matchingSubscriptionsCount; + private Boolean notificationSent; + private Boolean actionTriggered; + + // Constructors + public EventStreamDTO() {} + + public EventStreamDTO(UUID eventId, EventType eventType, EventSeverity severity, String sourceService) { + this.eventId = eventId; + this.eventType = eventType; + this.severity = severity; + this.sourceService = sourceService; + this.timestamp = Instant.now(); + } + + // Getters and Setters + + public UUID getEventId() { + return eventId; + } + + public void setEventId(UUID eventId) { + this.eventId = eventId; + } + + public EventType getEventType() { + return eventType; + } + + public void setEventType(EventType eventType) { + this.eventType = eventType; + } + + public EventSeverity getSeverity() { + return severity; + } + + public void setSeverity(EventSeverity severity) { + this.severity = severity; + } + + public String getSourceService() { + return sourceService; + } + + public void setSourceService(String sourceService) { + this.sourceService = sourceService; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public UUID getAssetId() { + return assetId; + } + + public void setAssetId(UUID assetId) { + this.assetId = assetId; + } + + public String getAssetName() { + return assetName; + } + + public void setAssetName(String assetName) { + this.assetName = assetName; + } + + public UUID getPolicyId() { + return policyId; + } + + public void setPolicyId(UUID policyId) { + this.policyId = policyId; + } + + public String getPolicyName() { + return policyName; + } + + public void setPolicyName(String policyName) { + this.policyName = policyName; + } + + public Map getEventData() { + return eventData; + } + + public void setEventData(Map eventData) { + this.eventData = eventData; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getUserAction() { + return userAction; + } + + public void setUserAction(String userAction) { + this.userAction = userAction; + } + + public Boolean getIsProcessed() { + return isProcessed; + } + + public void setIsProcessed(Boolean isProcessed) { + this.isProcessed = isProcessed; + } + + public Integer getMatchingSubscriptionsCount() { + return matchingSubscriptionsCount; + } + + public void setMatchingSubscriptionsCount(Integer matchingSubscriptionsCount) { + this.matchingSubscriptionsCount = matchingSubscriptionsCount; + } + + public Boolean getNotificationSent() { + return notificationSent; + } + + public void setNotificationSent(Boolean notificationSent) { + this.notificationSent = notificationSent; + } + + public Boolean getActionTriggered() { + return actionTriggered; + } + + public void setActionTriggered(Boolean actionTriggered) { + this.actionTriggered = actionTriggered; + } + + @Override + public String toString() { + return "EventStreamDTO{" + + "eventId=" + eventId + + ", eventType=" + eventType + + ", severity=" + severity + + ", sourceService='" + sourceService + '\'' + + ", title='" + title + '\'' + + ", assetId=" + assetId + + ", policyId=" + policyId + + ", timestamp=" + timestamp + + ", isProcessed=" + isProcessed + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/EventSubscriptionInputDTO.java b/src/main/java/com/dalab/policyengine/dto/EventSubscriptionInputDTO.java index d565f8e33bf536ee4d7897c03a115f6793a453da..8ca70b97a5c213bd2799753acd4294c61236998b 100644 --- a/src/main/java/com/dalab/policyengine/dto/EventSubscriptionInputDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/EventSubscriptionInputDTO.java @@ -1,229 +1,229 @@ -package com.dalab.policyengine.dto; - -import java.util.List; -import java.util.Map; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -import com.dalab.policyengine.model.EventSeverity; -import com.dalab.policyengine.model.EventType; - -/** - * DTO for creating or updating event subscriptions. - */ -public class EventSubscriptionInputDTO { - - @NotBlank(message = "Subscription name is required") - @Size(max = 255, message = "Subscription name must not exceed 255 characters") - private String name; - - @Size(max = 1000, message = "Description must not exceed 1000 characters") - private String description; - - /** - * Event types to subscribe to - */ - private List eventTypes; - - /** - * Event severity levels to include - */ - private List severities; - - /** - * Source services to monitor - */ - private List sourceServices; - - /** - * Event filtering rules - */ - private List rules; - - /** - * Notification configuration - * Example: { "email": true, "slack": { "channel": "#alerts", "webhook": "https://..." } } - */ - private Map notificationConfig; - - /** - * Action configuration for automated responses - * Example: { "autoQuarantine": true, "escalateTo": "admin", "maxRetries": 3 } - */ - private Map actionConfig; - - // Constructors - public EventSubscriptionInputDTO() {} - - public EventSubscriptionInputDTO(String name) { - this.name = name; - } - - // Getters and Setters - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public List getEventTypes() { - return eventTypes; - } - - public void setEventTypes(List eventTypes) { - this.eventTypes = eventTypes; - } - - public List getSeverities() { - return severities; - } - - public void setSeverities(List severities) { - this.severities = severities; - } - - public List getSourceServices() { - return sourceServices; - } - - public void setSourceServices(List sourceServices) { - this.sourceServices = sourceServices; - } - - public List getRules() { - return rules; - } - - public void setRules(List rules) { - this.rules = rules; - } - - public Map getNotificationConfig() { - return notificationConfig; - } - - public void setNotificationConfig(Map notificationConfig) { - this.notificationConfig = notificationConfig; - } - - public Map getActionConfig() { - return actionConfig; - } - - public void setActionConfig(Map actionConfig) { - this.actionConfig = actionConfig; - } - - @Override - public String toString() { - return "EventSubscriptionInputDTO{" + - "name='" + name + '\'' + - ", description='" + description + '\'' + - ", eventTypes=" + eventTypes + - ", severities=" + severities + - ", sourceServices=" + sourceServices + - ", rulesCount=" + (rules != null ? rules.size() : 0) + - '}'; - } - - /** - * Nested DTO for event rules within subscription input - */ - public static class EventRuleInputDTO { - @NotBlank(message = "Rule name is required") - @Size(max = 255, message = "Rule name must not exceed 255 characters") - private String name; - - @Size(max = 500, message = "Rule description must not exceed 500 characters") - private String description; - - @NotBlank(message = "Rule condition is required") - private String condition; - - private Integer priority = 1; - - private Boolean enabled = true; - - private Map parameters; - - // Constructors - public EventRuleInputDTO() {} - - public EventRuleInputDTO(String name, String condition) { - this.name = name; - this.condition = condition; - } - - // Getters and Setters - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getCondition() { - return condition; - } - - public void setCondition(String condition) { - this.condition = condition; - } - - public Integer getPriority() { - return priority; - } - - public void setPriority(Integer priority) { - this.priority = priority; - } - - public Boolean getEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - public Map getParameters() { - return parameters; - } - - public void setParameters(Map parameters) { - this.parameters = parameters; - } - - @Override - public String toString() { - return "EventRuleInputDTO{" + - "name='" + name + '\'' + - ", condition='" + condition + '\'' + - ", priority=" + priority + - ", enabled=" + enabled + - '}'; - } - } +package com.dalab.policyengine.dto; + +import java.util.List; +import java.util.Map; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +import com.dalab.policyengine.model.EventSeverity; +import com.dalab.policyengine.model.EventType; + +/** + * DTO for creating or updating event subscriptions. + */ +public class EventSubscriptionInputDTO { + + @NotBlank(message = "Subscription name is required") + @Size(max = 255, message = "Subscription name must not exceed 255 characters") + private String name; + + @Size(max = 1000, message = "Description must not exceed 1000 characters") + private String description; + + /** + * Event types to subscribe to + */ + private List eventTypes; + + /** + * Event severity levels to include + */ + private List severities; + + /** + * Source services to monitor + */ + private List sourceServices; + + /** + * Event filtering rules + */ + private List rules; + + /** + * Notification configuration + * Example: { "email": true, "slack": { "channel": "#alerts", "webhook": "https://..." } } + */ + private Map notificationConfig; + + /** + * Action configuration for automated responses + * Example: { "autoQuarantine": true, "escalateTo": "admin", "maxRetries": 3 } + */ + private Map actionConfig; + + // Constructors + public EventSubscriptionInputDTO() {} + + public EventSubscriptionInputDTO(String name) { + this.name = name; + } + + // Getters and Setters + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getEventTypes() { + return eventTypes; + } + + public void setEventTypes(List eventTypes) { + this.eventTypes = eventTypes; + } + + public List getSeverities() { + return severities; + } + + public void setSeverities(List severities) { + this.severities = severities; + } + + public List getSourceServices() { + return sourceServices; + } + + public void setSourceServices(List sourceServices) { + this.sourceServices = sourceServices; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public Map getNotificationConfig() { + return notificationConfig; + } + + public void setNotificationConfig(Map notificationConfig) { + this.notificationConfig = notificationConfig; + } + + public Map getActionConfig() { + return actionConfig; + } + + public void setActionConfig(Map actionConfig) { + this.actionConfig = actionConfig; + } + + @Override + public String toString() { + return "EventSubscriptionInputDTO{" + + "name='" + name + '\'' + + ", description='" + description + '\'' + + ", eventTypes=" + eventTypes + + ", severities=" + severities + + ", sourceServices=" + sourceServices + + ", rulesCount=" + (rules != null ? rules.size() : 0) + + '}'; + } + + /** + * Nested DTO for event rules within subscription input + */ + public static class EventRuleInputDTO { + @NotBlank(message = "Rule name is required") + @Size(max = 255, message = "Rule name must not exceed 255 characters") + private String name; + + @Size(max = 500, message = "Rule description must not exceed 500 characters") + private String description; + + @NotBlank(message = "Rule condition is required") + private String condition; + + private Integer priority = 1; + + private Boolean enabled = true; + + private Map parameters; + + // Constructors + public EventRuleInputDTO() {} + + public EventRuleInputDTO(String name, String condition) { + this.name = name; + this.condition = condition; + } + + // Getters and Setters + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + @Override + public String toString() { + return "EventRuleInputDTO{" + + "name='" + name + '\'' + + ", condition='" + condition + '\'' + + ", priority=" + priority + + ", enabled=" + enabled + + '}'; + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/EventSubscriptionOutputDTO.java b/src/main/java/com/dalab/policyengine/dto/EventSubscriptionOutputDTO.java index 6de043ffb6e6c51ef50cee25dc83a8e2884d153d..41163e62004959c1511625768247432b833f6658 100644 --- a/src/main/java/com/dalab/policyengine/dto/EventSubscriptionOutputDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/EventSubscriptionOutputDTO.java @@ -1,343 +1,343 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import com.dalab.policyengine.model.EventSeverity; -import com.dalab.policyengine.model.EventSubscriptionStatus; -import com.dalab.policyengine.model.EventType; - -/** - * DTO for returning event subscription information. - */ -public class EventSubscriptionOutputDTO { - - private UUID id; - private String name; - private String description; - private UUID userId; - private EventSubscriptionStatus status; - private List eventTypes; - private List severities; - private List sourceServices; - private List rules; - private Map notificationConfig; - private Map actionConfig; - private Instant createdAt; - private Instant updatedAt; - private UUID createdByUserId; - private UUID updatedByUserId; - - // Statistics - private Long totalRulesCount; - private Long enabledRulesCount; - private Long eventsMatchedCount; - private Instant lastEventMatchedAt; - - // Constructors - public EventSubscriptionOutputDTO() {} - - public EventSubscriptionOutputDTO(UUID id, String name, UUID userId, EventSubscriptionStatus status) { - this.id = id; - this.name = name; - this.userId = userId; - this.status = status; - } - - // Getters and Setters - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public UUID getUserId() { - return userId; - } - - public void setUserId(UUID userId) { - this.userId = userId; - } - - public EventSubscriptionStatus getStatus() { - return status; - } - - public void setStatus(EventSubscriptionStatus status) { - this.status = status; - } - - public List getEventTypes() { - return eventTypes; - } - - public void setEventTypes(List eventTypes) { - this.eventTypes = eventTypes; - } - - public List getSeverities() { - return severities; - } - - public void setSeverities(List severities) { - this.severities = severities; - } - - public List getSourceServices() { - return sourceServices; - } - - public void setSourceServices(List sourceServices) { - this.sourceServices = sourceServices; - } - - public List getRules() { - return rules; - } - - public void setRules(List rules) { - this.rules = rules; - } - - public Map getNotificationConfig() { - return notificationConfig; - } - - public void setNotificationConfig(Map notificationConfig) { - this.notificationConfig = notificationConfig; - } - - public Map getActionConfig() { - return actionConfig; - } - - public void setActionConfig(Map actionConfig) { - this.actionConfig = actionConfig; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } - - public Long getTotalRulesCount() { - return totalRulesCount; - } - - public void setTotalRulesCount(Long totalRulesCount) { - this.totalRulesCount = totalRulesCount; - } - - public Long getEnabledRulesCount() { - return enabledRulesCount; - } - - public void setEnabledRulesCount(Long enabledRulesCount) { - this.enabledRulesCount = enabledRulesCount; - } - - public Long getEventsMatchedCount() { - return eventsMatchedCount; - } - - public void setEventsMatchedCount(Long eventsMatchedCount) { - this.eventsMatchedCount = eventsMatchedCount; - } - - public Instant getLastEventMatchedAt() { - return lastEventMatchedAt; - } - - public void setLastEventMatchedAt(Instant lastEventMatchedAt) { - this.lastEventMatchedAt = lastEventMatchedAt; - } - - @Override - public String toString() { - return "EventSubscriptionOutputDTO{" + - "id=" + id + - ", name='" + name + '\'' + - ", userId=" + userId + - ", status=" + status + - ", eventTypes=" + eventTypes + - ", severities=" + severities + - ", rulesCount=" + totalRulesCount + - ", eventsMatchedCount=" + eventsMatchedCount + - '}'; - } - - /** - * Nested DTO for event rules within subscription output - */ - public static class EventRuleOutputDTO { - private UUID id; - private String name; - private String description; - private String condition; - private Integer priority; - private Boolean enabled; - private Map parameters; - private Instant createdAt; - private Instant updatedAt; - private UUID createdByUserId; - private UUID updatedByUserId; - - // Constructors - public EventRuleOutputDTO() {} - - public EventRuleOutputDTO(UUID id, String name, String condition) { - this.id = id; - this.name = name; - this.condition = condition; - } - - // Getters and Setters - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getCondition() { - return condition; - } - - public void setCondition(String condition) { - this.condition = condition; - } - - public Integer getPriority() { - return priority; - } - - public void setPriority(Integer priority) { - this.priority = priority; - } - - public Boolean getEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - public Map getParameters() { - return parameters; - } - - public void setParameters(Map parameters) { - this.parameters = parameters; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } - - @Override - public String toString() { - return "EventRuleOutputDTO{" + - "id=" + id + - ", name='" + name + '\'' + - ", condition='" + condition + '\'' + - ", priority=" + priority + - ", enabled=" + enabled + - '}'; - } - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.dalab.policyengine.model.EventSeverity; +import com.dalab.policyengine.model.EventSubscriptionStatus; +import com.dalab.policyengine.model.EventType; + +/** + * DTO for returning event subscription information. + */ +public class EventSubscriptionOutputDTO { + + private UUID id; + private String name; + private String description; + private UUID userId; + private EventSubscriptionStatus status; + private List eventTypes; + private List severities; + private List sourceServices; + private List rules; + private Map notificationConfig; + private Map actionConfig; + private Instant createdAt; + private Instant updatedAt; + private UUID createdByUserId; + private UUID updatedByUserId; + + // Statistics + private Long totalRulesCount; + private Long enabledRulesCount; + private Long eventsMatchedCount; + private Instant lastEventMatchedAt; + + // Constructors + public EventSubscriptionOutputDTO() {} + + public EventSubscriptionOutputDTO(UUID id, String name, UUID userId, EventSubscriptionStatus status) { + this.id = id; + this.name = name; + this.userId = userId; + this.status = status; + } + + // Getters and Setters + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public EventSubscriptionStatus getStatus() { + return status; + } + + public void setStatus(EventSubscriptionStatus status) { + this.status = status; + } + + public List getEventTypes() { + return eventTypes; + } + + public void setEventTypes(List eventTypes) { + this.eventTypes = eventTypes; + } + + public List getSeverities() { + return severities; + } + + public void setSeverities(List severities) { + this.severities = severities; + } + + public List getSourceServices() { + return sourceServices; + } + + public void setSourceServices(List sourceServices) { + this.sourceServices = sourceServices; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public Map getNotificationConfig() { + return notificationConfig; + } + + public void setNotificationConfig(Map notificationConfig) { + this.notificationConfig = notificationConfig; + } + + public Map getActionConfig() { + return actionConfig; + } + + public void setActionConfig(Map actionConfig) { + this.actionConfig = actionConfig; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } + + public Long getTotalRulesCount() { + return totalRulesCount; + } + + public void setTotalRulesCount(Long totalRulesCount) { + this.totalRulesCount = totalRulesCount; + } + + public Long getEnabledRulesCount() { + return enabledRulesCount; + } + + public void setEnabledRulesCount(Long enabledRulesCount) { + this.enabledRulesCount = enabledRulesCount; + } + + public Long getEventsMatchedCount() { + return eventsMatchedCount; + } + + public void setEventsMatchedCount(Long eventsMatchedCount) { + this.eventsMatchedCount = eventsMatchedCount; + } + + public Instant getLastEventMatchedAt() { + return lastEventMatchedAt; + } + + public void setLastEventMatchedAt(Instant lastEventMatchedAt) { + this.lastEventMatchedAt = lastEventMatchedAt; + } + + @Override + public String toString() { + return "EventSubscriptionOutputDTO{" + + "id=" + id + + ", name='" + name + '\'' + + ", userId=" + userId + + ", status=" + status + + ", eventTypes=" + eventTypes + + ", severities=" + severities + + ", rulesCount=" + totalRulesCount + + ", eventsMatchedCount=" + eventsMatchedCount + + '}'; + } + + /** + * Nested DTO for event rules within subscription output + */ + public static class EventRuleOutputDTO { + private UUID id; + private String name; + private String description; + private String condition; + private Integer priority; + private Boolean enabled; + private Map parameters; + private Instant createdAt; + private Instant updatedAt; + private UUID createdByUserId; + private UUID updatedByUserId; + + // Constructors + public EventRuleOutputDTO() {} + + public EventRuleOutputDTO(UUID id, String name, String condition) { + this.id = id; + this.name = name; + this.condition = condition; + } + + // Getters and Setters + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } + + @Override + public String toString() { + return "EventRuleOutputDTO{" + + "id=" + id + + ", name='" + name + '\'' + + ", condition='" + condition + '\'' + + ", priority=" + priority + + ", enabled=" + enabled + + '}'; + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyDraftActionDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyDraftActionDTO.java index 113cad153305bb7ab5a6af1f35c5d5b19338d0e7..8d2b5e1f33660fd03d3b7257fe300592564f91f7 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyDraftActionDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyDraftActionDTO.java @@ -1,268 +1,268 @@ -package com.dalab.policyengine.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * DTO for policy draft workflow actions (submit, approve, reject, etc.). - * Used for all workflow transition operations. - */ -public class PolicyDraftActionDTO { - - /** - * The action to perform (SUBMIT, APPROVE, REJECT, REQUEST_CHANGES, ARCHIVE). - */ - @NotBlank(message = "Action type is required") - private String actionType; - - /** - * Comment or reason for the action (required for rejection and optional for others). - */ - @Size(max = 2000, message = "Comment must not exceed 2000 characters") - private String comment; - - /** - * Priority level if submitting or approving (HIGH, MEDIUM, LOW). - */ - private String priority; - - /** - * Target implementation date if approving for publication. - */ - private String targetImplementationDate; - - /** - * Additional metadata for the action. - */ - private String metadata; - - // Constructors - public PolicyDraftActionDTO() {} - - public PolicyDraftActionDTO(String actionType) { - this.actionType = actionType; - } - - public PolicyDraftActionDTO(String actionType, String comment) { - this.actionType = actionType; - this.comment = comment; - } - - // Getters and Setters - public String getActionType() { - return actionType; - } - - public void setActionType(String actionType) { - this.actionType = actionType; - } - - public String getComment() { - return comment; - } - - public void setComment(String comment) { - this.comment = comment; - } - - public String getPriority() { - return priority; - } - - public void setPriority(String priority) { - this.priority = priority; - } - - public String getTargetImplementationDate() { - return targetImplementationDate; - } - - public void setTargetImplementationDate(String targetImplementationDate) { - this.targetImplementationDate = targetImplementationDate; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; - } -} - -/** - * DTO for bulk operations on multiple policy drafts. - */ -class PolicyDraftBulkActionDTO { - - /** - * List of draft IDs to perform action on. - */ - @NotBlank(message = "Draft IDs are required") - private java.util.List draftIds; - - /** - * The action to perform on all selected drafts. - */ - @NotBlank(message = "Action type is required") - private String actionType; - - /** - * Comment for the bulk action. - */ - @Size(max = 1000, message = "Comment must not exceed 1000 characters") - private String comment; - - // Constructors - public PolicyDraftBulkActionDTO() {} - - // Getters and Setters - public java.util.List getDraftIds() { - return draftIds; - } - - public void setDraftIds(java.util.List draftIds) { - this.draftIds = draftIds; - } - - public String getActionType() { - return actionType; - } - - public void setActionType(String actionType) { - this.actionType = actionType; - } - - public String getComment() { - return comment; - } - - public void setComment(String comment) { - this.comment = comment; - } -} - -/** - * DTO for policy draft summary information (for list views). - */ -class PolicyDraftSummaryDTO { - - private java.util.UUID id; - private String name; - private String status; - private Integer version; - private String priority; - private String category; - private java.time.Instant createdAt; - private java.time.Instant updatedAt; - private java.util.UUID createdByUserId; - private java.time.Instant targetImplementationDate; - private int reviewCommentsCount; - private boolean isOverdue; - - // Constructors - public PolicyDraftSummaryDTO() {} - - public PolicyDraftSummaryDTO(java.util.UUID id, String name, String status) { - this.id = id; - this.name = name; - this.status = status; - } - - // Getters and Setters - public java.util.UUID getId() { - return id; - } - - public void setId(java.util.UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } - - public String getPriority() { - return priority; - } - - public void setPriority(String priority) { - this.priority = priority; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public java.time.Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(java.time.Instant createdAt) { - this.createdAt = createdAt; - } - - public java.time.Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(java.time.Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public java.util.UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(java.util.UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public java.time.Instant getTargetImplementationDate() { - return targetImplementationDate; - } - - public void setTargetImplementationDate(java.time.Instant targetImplementationDate) { - this.targetImplementationDate = targetImplementationDate; - } - - public int getReviewCommentsCount() { - return reviewCommentsCount; - } - - public void setReviewCommentsCount(int reviewCommentsCount) { - this.reviewCommentsCount = reviewCommentsCount; - } - - public boolean isOverdue() { - return isOverdue; - } - - public void setOverdue(boolean overdue) { - isOverdue = overdue; - } +package com.dalab.policyengine.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * DTO for policy draft workflow actions (submit, approve, reject, etc.). + * Used for all workflow transition operations. + */ +public class PolicyDraftActionDTO { + + /** + * The action to perform (SUBMIT, APPROVE, REJECT, REQUEST_CHANGES, ARCHIVE). + */ + @NotBlank(message = "Action type is required") + private String actionType; + + /** + * Comment or reason for the action (required for rejection and optional for others). + */ + @Size(max = 2000, message = "Comment must not exceed 2000 characters") + private String comment; + + /** + * Priority level if submitting or approving (HIGH, MEDIUM, LOW). + */ + private String priority; + + /** + * Target implementation date if approving for publication. + */ + private String targetImplementationDate; + + /** + * Additional metadata for the action. + */ + private String metadata; + + // Constructors + public PolicyDraftActionDTO() {} + + public PolicyDraftActionDTO(String actionType) { + this.actionType = actionType; + } + + public PolicyDraftActionDTO(String actionType, String comment) { + this.actionType = actionType; + this.comment = comment; + } + + // Getters and Setters + public String getActionType() { + return actionType; + } + + public void setActionType(String actionType) { + this.actionType = actionType; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public String getTargetImplementationDate() { + return targetImplementationDate; + } + + public void setTargetImplementationDate(String targetImplementationDate) { + this.targetImplementationDate = targetImplementationDate; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } +} + +/** + * DTO for bulk operations on multiple policy drafts. + */ +class PolicyDraftBulkActionDTO { + + /** + * List of draft IDs to perform action on. + */ + @NotBlank(message = "Draft IDs are required") + private java.util.List draftIds; + + /** + * The action to perform on all selected drafts. + */ + @NotBlank(message = "Action type is required") + private String actionType; + + /** + * Comment for the bulk action. + */ + @Size(max = 1000, message = "Comment must not exceed 1000 characters") + private String comment; + + // Constructors + public PolicyDraftBulkActionDTO() {} + + // Getters and Setters + public java.util.List getDraftIds() { + return draftIds; + } + + public void setDraftIds(java.util.List draftIds) { + this.draftIds = draftIds; + } + + public String getActionType() { + return actionType; + } + + public void setActionType(String actionType) { + this.actionType = actionType; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } +} + +/** + * DTO for policy draft summary information (for list views). + */ +class PolicyDraftSummaryDTO { + + private java.util.UUID id; + private String name; + private String status; + private Integer version; + private String priority; + private String category; + private java.time.Instant createdAt; + private java.time.Instant updatedAt; + private java.util.UUID createdByUserId; + private java.time.Instant targetImplementationDate; + private int reviewCommentsCount; + private boolean isOverdue; + + // Constructors + public PolicyDraftSummaryDTO() {} + + public PolicyDraftSummaryDTO(java.util.UUID id, String name, String status) { + this.id = id; + this.name = name; + this.status = status; + } + + // Getters and Setters + public java.util.UUID getId() { + return id; + } + + public void setId(java.util.UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public java.time.Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(java.time.Instant createdAt) { + this.createdAt = createdAt; + } + + public java.time.Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(java.time.Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public java.util.UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(java.util.UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public java.time.Instant getTargetImplementationDate() { + return targetImplementationDate; + } + + public void setTargetImplementationDate(java.time.Instant targetImplementationDate) { + this.targetImplementationDate = targetImplementationDate; + } + + public int getReviewCommentsCount() { + return reviewCommentsCount; + } + + public void setReviewCommentsCount(int reviewCommentsCount) { + this.reviewCommentsCount = reviewCommentsCount; + } + + public boolean isOverdue() { + return isOverdue; + } + + public void setOverdue(boolean overdue) { + isOverdue = overdue; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyDraftInputDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyDraftInputDTO.java index b78d666e4d688ecca4abf009718f1297b4de61ad..5df3265fc848d56fc311a526b43d9ea2bcfc1cc3 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyDraftInputDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyDraftInputDTO.java @@ -1,227 +1,227 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * DTO for creating and updating policy drafts. - * Contains all information needed for draft management and workflow. - */ -public class PolicyDraftInputDTO { - - /** - * Name of the policy (must be unique when published). - */ - @NotBlank(message = "Policy name is required") - @Size(max = 255, message = "Policy name must not exceed 255 characters") - private String name; - - /** - * Detailed description of the policy's purpose and scope. - */ - @Size(max = 2000, message = "Description must not exceed 2000 characters") - private String description; - - /** - * Reference to the published policy if this is an update. - */ - private UUID basePolicyId; - - /** - * MVEL condition logic for the policy evaluation. - */ - private String conditionLogic; - - /** - * JSON representation of policy rules structure. - */ - private List> rulesDefinition; - - /** - * JSON representation of actions to be taken when policy is triggered. - */ - private Map actions; - - /** - * Change summary describing what was modified in this version. - */ - @Size(max = 1000, message = "Change summary must not exceed 1000 characters") - private String changeSummary; - - /** - * Justification for the policy changes or creation. - */ - @Size(max = 2000, message = "Justification must not exceed 2000 characters") - private String justification; - - /** - * Expected impact of implementing this policy. - */ - @Size(max = 1000, message = "Expected impact must not exceed 1000 characters") - private String expectedImpact; - - /** - * Target implementation date for the policy. - */ - private Instant targetImplementationDate; - - /** - * Priority level for policy implementation. - */ - private String priority = "MEDIUM"; - - /** - * Business category or domain this policy applies to. - */ - @Size(max = 100, message = "Category must not exceed 100 characters") - private String category; - - /** - * Tags for categorization and searchability. - */ - private List tags; - - /** - * Stakeholders who should be notified about this policy. - */ - private List stakeholders; - - /** - * Additional metadata for workflow management. - */ - private Map workflowMetadata; - - // Constructors - public PolicyDraftInputDTO() {} - - public PolicyDraftInputDTO(String name, String description) { - this.name = name; - this.description = description; - } - - // Getters and Setters - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public UUID getBasePolicyId() { - return basePolicyId; - } - - public void setBasePolicyId(UUID basePolicyId) { - this.basePolicyId = basePolicyId; - } - - public String getConditionLogic() { - return conditionLogic; - } - - public void setConditionLogic(String conditionLogic) { - this.conditionLogic = conditionLogic; - } - - public List> getRulesDefinition() { - return rulesDefinition; - } - - public void setRulesDefinition(List> rulesDefinition) { - this.rulesDefinition = rulesDefinition; - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } - - public String getChangeSummary() { - return changeSummary; - } - - public void setChangeSummary(String changeSummary) { - this.changeSummary = changeSummary; - } - - public String getJustification() { - return justification; - } - - public void setJustification(String justification) { - this.justification = justification; - } - - public String getExpectedImpact() { - return expectedImpact; - } - - public void setExpectedImpact(String expectedImpact) { - this.expectedImpact = expectedImpact; - } - - public Instant getTargetImplementationDate() { - return targetImplementationDate; - } - - public void setTargetImplementationDate(Instant targetImplementationDate) { - this.targetImplementationDate = targetImplementationDate; - } - - public String getPriority() { - return priority; - } - - public void setPriority(String priority) { - this.priority = priority; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - - public List getStakeholders() { - return stakeholders; - } - - public void setStakeholders(List stakeholders) { - this.stakeholders = stakeholders; - } - - public Map getWorkflowMetadata() { - return workflowMetadata; - } - - public void setWorkflowMetadata(Map workflowMetadata) { - this.workflowMetadata = workflowMetadata; - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * DTO for creating and updating policy drafts. + * Contains all information needed for draft management and workflow. + */ +public class PolicyDraftInputDTO { + + /** + * Name of the policy (must be unique when published). + */ + @NotBlank(message = "Policy name is required") + @Size(max = 255, message = "Policy name must not exceed 255 characters") + private String name; + + /** + * Detailed description of the policy's purpose and scope. + */ + @Size(max = 2000, message = "Description must not exceed 2000 characters") + private String description; + + /** + * Reference to the published policy if this is an update. + */ + private UUID basePolicyId; + + /** + * MVEL condition logic for the policy evaluation. + */ + private String conditionLogic; + + /** + * JSON representation of policy rules structure. + */ + private List> rulesDefinition; + + /** + * JSON representation of actions to be taken when policy is triggered. + */ + private Map actions; + + /** + * Change summary describing what was modified in this version. + */ + @Size(max = 1000, message = "Change summary must not exceed 1000 characters") + private String changeSummary; + + /** + * Justification for the policy changes or creation. + */ + @Size(max = 2000, message = "Justification must not exceed 2000 characters") + private String justification; + + /** + * Expected impact of implementing this policy. + */ + @Size(max = 1000, message = "Expected impact must not exceed 1000 characters") + private String expectedImpact; + + /** + * Target implementation date for the policy. + */ + private Instant targetImplementationDate; + + /** + * Priority level for policy implementation. + */ + private String priority = "MEDIUM"; + + /** + * Business category or domain this policy applies to. + */ + @Size(max = 100, message = "Category must not exceed 100 characters") + private String category; + + /** + * Tags for categorization and searchability. + */ + private List tags; + + /** + * Stakeholders who should be notified about this policy. + */ + private List stakeholders; + + /** + * Additional metadata for workflow management. + */ + private Map workflowMetadata; + + // Constructors + public PolicyDraftInputDTO() {} + + public PolicyDraftInputDTO(String name, String description) { + this.name = name; + this.description = description; + } + + // Getters and Setters + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public UUID getBasePolicyId() { + return basePolicyId; + } + + public void setBasePolicyId(UUID basePolicyId) { + this.basePolicyId = basePolicyId; + } + + public String getConditionLogic() { + return conditionLogic; + } + + public void setConditionLogic(String conditionLogic) { + this.conditionLogic = conditionLogic; + } + + public List> getRulesDefinition() { + return rulesDefinition; + } + + public void setRulesDefinition(List> rulesDefinition) { + this.rulesDefinition = rulesDefinition; + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } + + public String getChangeSummary() { + return changeSummary; + } + + public void setChangeSummary(String changeSummary) { + this.changeSummary = changeSummary; + } + + public String getJustification() { + return justification; + } + + public void setJustification(String justification) { + this.justification = justification; + } + + public String getExpectedImpact() { + return expectedImpact; + } + + public void setExpectedImpact(String expectedImpact) { + this.expectedImpact = expectedImpact; + } + + public Instant getTargetImplementationDate() { + return targetImplementationDate; + } + + public void setTargetImplementationDate(Instant targetImplementationDate) { + this.targetImplementationDate = targetImplementationDate; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public List getStakeholders() { + return stakeholders; + } + + public void setStakeholders(List stakeholders) { + this.stakeholders = stakeholders; + } + + public Map getWorkflowMetadata() { + return workflowMetadata; + } + + public void setWorkflowMetadata(Map workflowMetadata) { + this.workflowMetadata = workflowMetadata; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyDraftOutputDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyDraftOutputDTO.java index a83370796cb3c005450d6f100f34071647d7e5a8..66c40ec7977d6f2b18ab4cd4095657270ce06410 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyDraftOutputDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyDraftOutputDTO.java @@ -1,412 +1,412 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * DTO for returning policy draft information including workflow status and audit trail. - * Contains comprehensive information for UI display and workflow management. - */ -public class PolicyDraftOutputDTO { - - private UUID id; - private String name; - private String description; - private String status; - private Integer version; - private UUID basePolicyId; - private String conditionLogic; - private List> rulesDefinition; - private Map actions; - private String changeSummary; - private String justification; - private String expectedImpact; - private Instant targetImplementationDate; - private String priority; - private String category; - private List tags; - private List stakeholders; - private Map approvalMetadata; - private List> reviewComments; - - // Audit trail information - private Instant createdAt; - private Instant updatedAt; - private UUID createdByUserId; - private UUID updatedByUserId; - private Instant submittedAt; - private UUID submittedByUserId; - private Instant approvedAt; - private UUID approvedByUserId; - private Instant rejectedAt; - private UUID rejectedByUserId; - private Instant publishedAt; - private UUID publishedByUserId; - - // Enhanced workflow information - private WorkflowStatusDTO workflowStatus; - private List availableActions; - - // Constructors - public PolicyDraftOutputDTO() {} - - public PolicyDraftOutputDTO(UUID id, String name, String status) { - this.id = id; - this.name = name; - this.status = status; - } - - // Getters and Setters - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } - - public UUID getBasePolicyId() { - return basePolicyId; - } - - public void setBasePolicyId(UUID basePolicyId) { - this.basePolicyId = basePolicyId; - } - - public String getConditionLogic() { - return conditionLogic; - } - - public void setConditionLogic(String conditionLogic) { - this.conditionLogic = conditionLogic; - } - - public List> getRulesDefinition() { - return rulesDefinition; - } - - public void setRulesDefinition(List> rulesDefinition) { - this.rulesDefinition = rulesDefinition; - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } - - public String getChangeSummary() { - return changeSummary; - } - - public void setChangeSummary(String changeSummary) { - this.changeSummary = changeSummary; - } - - public String getJustification() { - return justification; - } - - public void setJustification(String justification) { - this.justification = justification; - } - - public String getExpectedImpact() { - return expectedImpact; - } - - public void setExpectedImpact(String expectedImpact) { - this.expectedImpact = expectedImpact; - } - - public Instant getTargetImplementationDate() { - return targetImplementationDate; - } - - public void setTargetImplementationDate(Instant targetImplementationDate) { - this.targetImplementationDate = targetImplementationDate; - } - - public String getPriority() { - return priority; - } - - public void setPriority(String priority) { - this.priority = priority; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - - public List getStakeholders() { - return stakeholders; - } - - public void setStakeholders(List stakeholders) { - this.stakeholders = stakeholders; - } - - public Map getApprovalMetadata() { - return approvalMetadata; - } - - public void setApprovalMetadata(Map approvalMetadata) { - this.approvalMetadata = approvalMetadata; - } - - public List> getReviewComments() { - return reviewComments; - } - - public void setReviewComments(List> reviewComments) { - this.reviewComments = reviewComments; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } - - public Instant getSubmittedAt() { - return submittedAt; - } - - public void setSubmittedAt(Instant submittedAt) { - this.submittedAt = submittedAt; - } - - public UUID getSubmittedByUserId() { - return submittedByUserId; - } - - public void setSubmittedByUserId(UUID submittedByUserId) { - this.submittedByUserId = submittedByUserId; - } - - public Instant getApprovedAt() { - return approvedAt; - } - - public void setApprovedAt(Instant approvedAt) { - this.approvedAt = approvedAt; - } - - public UUID getApprovedByUserId() { - return approvedByUserId; - } - - public void setApprovedByUserId(UUID approvedByUserId) { - this.approvedByUserId = approvedByUserId; - } - - public Instant getRejectedAt() { - return rejectedAt; - } - - public void setRejectedAt(Instant rejectedAt) { - this.rejectedAt = rejectedAt; - } - - public UUID getRejectedByUserId() { - return rejectedByUserId; - } - - public void setRejectedByUserId(UUID rejectedByUserId) { - this.rejectedByUserId = rejectedByUserId; - } - - public Instant getPublishedAt() { - return publishedAt; - } - - public void setPublishedAt(Instant publishedAt) { - this.publishedAt = publishedAt; - } - - public UUID getPublishedByUserId() { - return publishedByUserId; - } - - public void setPublishedByUserId(UUID publishedByUserId) { - this.publishedByUserId = publishedByUserId; - } - - public WorkflowStatusDTO getWorkflowStatus() { - return workflowStatus; - } - - public void setWorkflowStatus(WorkflowStatusDTO workflowStatus) { - this.workflowStatus = workflowStatus; - } - - public List getAvailableActions() { - return availableActions; - } - - public void setAvailableActions(List availableActions) { - this.availableActions = availableActions; - } - - /** - * Nested DTO for workflow status information. - */ - public static class WorkflowStatusDTO { - private String currentStage; - private String statusDescription; - private List nextPossibleStates; - private boolean canEdit; - private boolean canSubmit; - private boolean canApprove; - private boolean canReject; - private boolean canPublish; - private String stageColor; // For UI status indicators - private Integer daysInCurrentStatus; - - // Constructors, getters, and setters - public WorkflowStatusDTO() {} - - public String getCurrentStage() { return currentStage; } - public void setCurrentStage(String currentStage) { this.currentStage = currentStage; } - - public String getStatusDescription() { return statusDescription; } - public void setStatusDescription(String statusDescription) { this.statusDescription = statusDescription; } - - public List getNextPossibleStates() { return nextPossibleStates; } - public void setNextPossibleStates(List nextPossibleStates) { this.nextPossibleStates = nextPossibleStates; } - - public boolean isCanEdit() { return canEdit; } - public void setCanEdit(boolean canEdit) { this.canEdit = canEdit; } - - public boolean isCanSubmit() { return canSubmit; } - public void setCanSubmit(boolean canSubmit) { this.canSubmit = canSubmit; } - - public boolean isCanApprove() { return canApprove; } - public void setCanApprove(boolean canApprove) { this.canApprove = canApprove; } - - public boolean isCanReject() { return canReject; } - public void setCanReject(boolean canReject) { this.canReject = canReject; } - - public boolean isCanPublish() { return canPublish; } - public void setCanPublish(boolean canPublish) { this.canPublish = canPublish; } - - public String getStageColor() { return stageColor; } - public void setStageColor(String stageColor) { this.stageColor = stageColor; } - - public Integer getDaysInCurrentStatus() { return daysInCurrentStatus; } - public void setDaysInCurrentStatus(Integer daysInCurrentStatus) { this.daysInCurrentStatus = daysInCurrentStatus; } - } - - /** - * Nested DTO for available workflow actions. - */ - public static class WorkflowActionDTO { - private String actionType; - private String actionLabel; - private String actionDescription; - private boolean requiresComment; - private String confirmationMessage; - private String buttonStyle; // For UI styling - - // Constructors, getters, and setters - public WorkflowActionDTO() {} - - public WorkflowActionDTO(String actionType, String actionLabel) { - this.actionType = actionType; - this.actionLabel = actionLabel; - } - - public String getActionType() { return actionType; } - public void setActionType(String actionType) { this.actionType = actionType; } - - public String getActionLabel() { return actionLabel; } - public void setActionLabel(String actionLabel) { this.actionLabel = actionLabel; } - - public String getActionDescription() { return actionDescription; } - public void setActionDescription(String actionDescription) { this.actionDescription = actionDescription; } - - public boolean isRequiresComment() { return requiresComment; } - public void setRequiresComment(boolean requiresComment) { this.requiresComment = requiresComment; } - - public String getConfirmationMessage() { return confirmationMessage; } - public void setConfirmationMessage(String confirmationMessage) { this.confirmationMessage = confirmationMessage; } - - public String getButtonStyle() { return buttonStyle; } - public void setButtonStyle(String buttonStyle) { this.buttonStyle = buttonStyle; } - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * DTO for returning policy draft information including workflow status and audit trail. + * Contains comprehensive information for UI display and workflow management. + */ +public class PolicyDraftOutputDTO { + + private UUID id; + private String name; + private String description; + private String status; + private Integer version; + private UUID basePolicyId; + private String conditionLogic; + private List> rulesDefinition; + private Map actions; + private String changeSummary; + private String justification; + private String expectedImpact; + private Instant targetImplementationDate; + private String priority; + private String category; + private List tags; + private List stakeholders; + private Map approvalMetadata; + private List> reviewComments; + + // Audit trail information + private Instant createdAt; + private Instant updatedAt; + private UUID createdByUserId; + private UUID updatedByUserId; + private Instant submittedAt; + private UUID submittedByUserId; + private Instant approvedAt; + private UUID approvedByUserId; + private Instant rejectedAt; + private UUID rejectedByUserId; + private Instant publishedAt; + private UUID publishedByUserId; + + // Enhanced workflow information + private WorkflowStatusDTO workflowStatus; + private List availableActions; + + // Constructors + public PolicyDraftOutputDTO() {} + + public PolicyDraftOutputDTO(UUID id, String name, String status) { + this.id = id; + this.name = name; + this.status = status; + } + + // Getters and Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public UUID getBasePolicyId() { + return basePolicyId; + } + + public void setBasePolicyId(UUID basePolicyId) { + this.basePolicyId = basePolicyId; + } + + public String getConditionLogic() { + return conditionLogic; + } + + public void setConditionLogic(String conditionLogic) { + this.conditionLogic = conditionLogic; + } + + public List> getRulesDefinition() { + return rulesDefinition; + } + + public void setRulesDefinition(List> rulesDefinition) { + this.rulesDefinition = rulesDefinition; + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } + + public String getChangeSummary() { + return changeSummary; + } + + public void setChangeSummary(String changeSummary) { + this.changeSummary = changeSummary; + } + + public String getJustification() { + return justification; + } + + public void setJustification(String justification) { + this.justification = justification; + } + + public String getExpectedImpact() { + return expectedImpact; + } + + public void setExpectedImpact(String expectedImpact) { + this.expectedImpact = expectedImpact; + } + + public Instant getTargetImplementationDate() { + return targetImplementationDate; + } + + public void setTargetImplementationDate(Instant targetImplementationDate) { + this.targetImplementationDate = targetImplementationDate; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public List getStakeholders() { + return stakeholders; + } + + public void setStakeholders(List stakeholders) { + this.stakeholders = stakeholders; + } + + public Map getApprovalMetadata() { + return approvalMetadata; + } + + public void setApprovalMetadata(Map approvalMetadata) { + this.approvalMetadata = approvalMetadata; + } + + public List> getReviewComments() { + return reviewComments; + } + + public void setReviewComments(List> reviewComments) { + this.reviewComments = reviewComments; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } + + public Instant getSubmittedAt() { + return submittedAt; + } + + public void setSubmittedAt(Instant submittedAt) { + this.submittedAt = submittedAt; + } + + public UUID getSubmittedByUserId() { + return submittedByUserId; + } + + public void setSubmittedByUserId(UUID submittedByUserId) { + this.submittedByUserId = submittedByUserId; + } + + public Instant getApprovedAt() { + return approvedAt; + } + + public void setApprovedAt(Instant approvedAt) { + this.approvedAt = approvedAt; + } + + public UUID getApprovedByUserId() { + return approvedByUserId; + } + + public void setApprovedByUserId(UUID approvedByUserId) { + this.approvedByUserId = approvedByUserId; + } + + public Instant getRejectedAt() { + return rejectedAt; + } + + public void setRejectedAt(Instant rejectedAt) { + this.rejectedAt = rejectedAt; + } + + public UUID getRejectedByUserId() { + return rejectedByUserId; + } + + public void setRejectedByUserId(UUID rejectedByUserId) { + this.rejectedByUserId = rejectedByUserId; + } + + public Instant getPublishedAt() { + return publishedAt; + } + + public void setPublishedAt(Instant publishedAt) { + this.publishedAt = publishedAt; + } + + public UUID getPublishedByUserId() { + return publishedByUserId; + } + + public void setPublishedByUserId(UUID publishedByUserId) { + this.publishedByUserId = publishedByUserId; + } + + public WorkflowStatusDTO getWorkflowStatus() { + return workflowStatus; + } + + public void setWorkflowStatus(WorkflowStatusDTO workflowStatus) { + this.workflowStatus = workflowStatus; + } + + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } + + /** + * Nested DTO for workflow status information. + */ + public static class WorkflowStatusDTO { + private String currentStage; + private String statusDescription; + private List nextPossibleStates; + private boolean canEdit; + private boolean canSubmit; + private boolean canApprove; + private boolean canReject; + private boolean canPublish; + private String stageColor; // For UI status indicators + private Integer daysInCurrentStatus; + + // Constructors, getters, and setters + public WorkflowStatusDTO() {} + + public String getCurrentStage() { return currentStage; } + public void setCurrentStage(String currentStage) { this.currentStage = currentStage; } + + public String getStatusDescription() { return statusDescription; } + public void setStatusDescription(String statusDescription) { this.statusDescription = statusDescription; } + + public List getNextPossibleStates() { return nextPossibleStates; } + public void setNextPossibleStates(List nextPossibleStates) { this.nextPossibleStates = nextPossibleStates; } + + public boolean isCanEdit() { return canEdit; } + public void setCanEdit(boolean canEdit) { this.canEdit = canEdit; } + + public boolean isCanSubmit() { return canSubmit; } + public void setCanSubmit(boolean canSubmit) { this.canSubmit = canSubmit; } + + public boolean isCanApprove() { return canApprove; } + public void setCanApprove(boolean canApprove) { this.canApprove = canApprove; } + + public boolean isCanReject() { return canReject; } + public void setCanReject(boolean canReject) { this.canReject = canReject; } + + public boolean isCanPublish() { return canPublish; } + public void setCanPublish(boolean canPublish) { this.canPublish = canPublish; } + + public String getStageColor() { return stageColor; } + public void setStageColor(String stageColor) { this.stageColor = stageColor; } + + public Integer getDaysInCurrentStatus() { return daysInCurrentStatus; } + public void setDaysInCurrentStatus(Integer daysInCurrentStatus) { this.daysInCurrentStatus = daysInCurrentStatus; } + } + + /** + * Nested DTO for available workflow actions. + */ + public static class WorkflowActionDTO { + private String actionType; + private String actionLabel; + private String actionDescription; + private boolean requiresComment; + private String confirmationMessage; + private String buttonStyle; // For UI styling + + // Constructors, getters, and setters + public WorkflowActionDTO() {} + + public WorkflowActionDTO(String actionType, String actionLabel) { + this.actionType = actionType; + this.actionLabel = actionLabel; + } + + public String getActionType() { return actionType; } + public void setActionType(String actionType) { this.actionType = actionType; } + + public String getActionLabel() { return actionLabel; } + public void setActionLabel(String actionLabel) { this.actionLabel = actionLabel; } + + public String getActionDescription() { return actionDescription; } + public void setActionDescription(String actionDescription) { this.actionDescription = actionDescription; } + + public boolean isRequiresComment() { return requiresComment; } + public void setRequiresComment(boolean requiresComment) { this.requiresComment = requiresComment; } + + public String getConfirmationMessage() { return confirmationMessage; } + public void setConfirmationMessage(String confirmationMessage) { this.confirmationMessage = confirmationMessage; } + + public String getButtonStyle() { return buttonStyle; } + public void setButtonStyle(String buttonStyle) { this.buttonStyle = buttonStyle; } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationOutputDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationOutputDTO.java index 8e91f8262c7fc4a545ca5b684772d4fe2f8b17a6..27c60f05e6139142aa30bd1aa1719d65521c4d93 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationOutputDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationOutputDTO.java @@ -1,92 +1,92 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -import com.dalab.policyengine.model.PolicyEvaluationStatus; - -public class PolicyEvaluationOutputDTO { - private UUID id; - private UUID policyId; - private String policyName; // For convenience - private String targetAssetId; - private PolicyEvaluationStatus status; - private Map evaluationDetails; // e.g., facts used, rules evaluated, outcome per rule - private Map triggeredActions; // Actions taken based on the policy - private Instant evaluatedAt; - private UUID evaluationTriggeredByUserId; - - // Getters and Setters - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getPolicyId() { - return policyId; - } - - public void setPolicyId(UUID policyId) { - this.policyId = policyId; - } - - public String getPolicyName() { - return policyName; - } - - public void setPolicyName(String policyName) { - this.policyName = policyName; - } - - public String getTargetAssetId() { - return targetAssetId; - } - - public void setTargetAssetId(String targetAssetId) { - this.targetAssetId = targetAssetId; - } - - public PolicyEvaluationStatus getStatus() { - return status; - } - - public void setStatus(PolicyEvaluationStatus status) { - this.status = status; - } - - public Map getEvaluationDetails() { - return evaluationDetails; - } - - public void setEvaluationDetails(Map evaluationDetails) { - this.evaluationDetails = evaluationDetails; - } - - public Map getTriggeredActions() { - return triggeredActions; - } - - public void setTriggeredActions(Map triggeredActions) { - this.triggeredActions = triggeredActions; - } - - public Instant getEvaluatedAt() { - return evaluatedAt; - } - - public void setEvaluatedAt(Instant evaluatedAt) { - this.evaluatedAt = evaluatedAt; - } - - public UUID getEvaluationTriggeredByUserId() { - return evaluationTriggeredByUserId; - } - - public void setEvaluationTriggeredByUserId(UUID evaluationTriggeredByUserId) { - this.evaluationTriggeredByUserId = evaluationTriggeredByUserId; - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import com.dalab.policyengine.model.PolicyEvaluationStatus; + +public class PolicyEvaluationOutputDTO { + private UUID id; + private UUID policyId; + private String policyName; // For convenience + private String targetAssetId; + private PolicyEvaluationStatus status; + private Map evaluationDetails; // e.g., facts used, rules evaluated, outcome per rule + private Map triggeredActions; // Actions taken based on the policy + private Instant evaluatedAt; + private UUID evaluationTriggeredByUserId; + + // Getters and Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getPolicyId() { + return policyId; + } + + public void setPolicyId(UUID policyId) { + this.policyId = policyId; + } + + public String getPolicyName() { + return policyName; + } + + public void setPolicyName(String policyName) { + this.policyName = policyName; + } + + public String getTargetAssetId() { + return targetAssetId; + } + + public void setTargetAssetId(String targetAssetId) { + this.targetAssetId = targetAssetId; + } + + public PolicyEvaluationStatus getStatus() { + return status; + } + + public void setStatus(PolicyEvaluationStatus status) { + this.status = status; + } + + public Map getEvaluationDetails() { + return evaluationDetails; + } + + public void setEvaluationDetails(Map evaluationDetails) { + this.evaluationDetails = evaluationDetails; + } + + public Map getTriggeredActions() { + return triggeredActions; + } + + public void setTriggeredActions(Map triggeredActions) { + this.triggeredActions = triggeredActions; + } + + public Instant getEvaluatedAt() { + return evaluatedAt; + } + + public void setEvaluatedAt(Instant evaluatedAt) { + this.evaluatedAt = evaluatedAt; + } + + public UUID getEvaluationTriggeredByUserId() { + return evaluationTriggeredByUserId; + } + + public void setEvaluationTriggeredByUserId(UUID evaluationTriggeredByUserId) { + this.evaluationTriggeredByUserId = evaluationTriggeredByUserId; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationRequestDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationRequestDTO.java index bbd35538c9bf548ea9f850550091c8c19b999919..8537c3063e42240c83ba128d50feb7d3abcbdd7d 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationRequestDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationRequestDTO.java @@ -1,32 +1,32 @@ -package com.dalab.policyengine.dto; - -import java.util.Map; - -import jakarta.validation.constraints.NotBlank; - -public class PolicyEvaluationRequestDTO { - - @NotBlank - private String targetAssetId; // The ID of the asset to be evaluated (e.g., from da-catalog) - - // Optional: Additional context or facts that might not be directly part of the asset - // but are relevant for this specific evaluation. - private Map evaluationContext; - - // Getters and Setters - public String getTargetAssetId() { - return targetAssetId; - } - - public void setTargetAssetId(String targetAssetId) { - this.targetAssetId = targetAssetId; - } - - public Map getEvaluationContext() { - return evaluationContext; - } - - public void setEvaluationContext(Map evaluationContext) { - this.evaluationContext = evaluationContext; - } +package com.dalab.policyengine.dto; + +import java.util.Map; + +import jakarta.validation.constraints.NotBlank; + +public class PolicyEvaluationRequestDTO { + + @NotBlank + private String targetAssetId; // The ID of the asset to be evaluated (e.g., from da-catalog) + + // Optional: Additional context or facts that might not be directly part of the asset + // but are relevant for this specific evaluation. + private Map evaluationContext; + + // Getters and Setters + public String getTargetAssetId() { + return targetAssetId; + } + + public void setTargetAssetId(String targetAssetId) { + this.targetAssetId = targetAssetId; + } + + public Map getEvaluationContext() { + return evaluationContext; + } + + public void setEvaluationContext(Map evaluationContext) { + this.evaluationContext = evaluationContext; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationSummaryDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationSummaryDTO.java index 1603ef6a1cc2aaa66bf9b56a4e7fd65349d0f89a..b7312191e4e33667a9c60fa97f04e6d0b8ce0607 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationSummaryDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyEvaluationSummaryDTO.java @@ -1,64 +1,64 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.UUID; - -import com.dalab.policyengine.model.PolicyEvaluationStatus; - -public class PolicyEvaluationSummaryDTO { - private UUID id; - private UUID policyId; - private String policyName; - private String targetAssetId; - private PolicyEvaluationStatus status; - private Instant evaluatedAt; - - // Getters and Setters - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getPolicyId() { - return policyId; - } - - public void setPolicyId(UUID policyId) { - this.policyId = policyId; - } - - public String getPolicyName() { - return policyName; - } - - public void setPolicyName(String policyName) { - this.policyName = policyName; - } - - public String getTargetAssetId() { - return targetAssetId; - } - - public void setTargetAssetId(String targetAssetId) { - this.targetAssetId = targetAssetId; - } - - public PolicyEvaluationStatus getStatus() { - return status; - } - - public void setStatus(PolicyEvaluationStatus status) { - this.status = status; - } - - public Instant getEvaluatedAt() { - return evaluatedAt; - } - - public void setEvaluatedAt(Instant evaluatedAt) { - this.evaluatedAt = evaluatedAt; - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.UUID; + +import com.dalab.policyengine.model.PolicyEvaluationStatus; + +public class PolicyEvaluationSummaryDTO { + private UUID id; + private UUID policyId; + private String policyName; + private String targetAssetId; + private PolicyEvaluationStatus status; + private Instant evaluatedAt; + + // Getters and Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getPolicyId() { + return policyId; + } + + public void setPolicyId(UUID policyId) { + this.policyId = policyId; + } + + public String getPolicyName() { + return policyName; + } + + public void setPolicyName(String policyName) { + this.policyName = policyName; + } + + public String getTargetAssetId() { + return targetAssetId; + } + + public void setTargetAssetId(String targetAssetId) { + this.targetAssetId = targetAssetId; + } + + public PolicyEvaluationStatus getStatus() { + return status; + } + + public void setStatus(PolicyEvaluationStatus status) { + this.status = status; + } + + public Instant getEvaluatedAt() { + return evaluatedAt; + } + + public void setEvaluatedAt(Instant evaluatedAt) { + this.evaluatedAt = evaluatedAt; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyImpactRequestDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyImpactRequestDTO.java index 405ba7f50367da5a547ecb5d017035f33cd3ab61..3b47ea1b0d93873e34b3767944e7703773fb5ffd 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyImpactRequestDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyImpactRequestDTO.java @@ -1,211 +1,211 @@ -package com.dalab.policyengine.dto; - -import java.util.List; -import java.util.Map; - -import jakarta.validation.constraints.NotBlank; - -/** - * DTO for requesting policy impact analysis preview. - * This allows users to see what assets would be affected by policy changes before implementation. - */ -public class PolicyImpactRequestDTO { - - /** - * The policy rules content to analyze for impact (JSON format). - * Required field containing the policy rules that need impact analysis. - */ - @NotBlank(message = "Policy rules content is required") - private String rulesContent; - - /** - * Type of analysis to perform: FULL, QUICK, or TARGETED. - * - FULL: Comprehensive analysis across all assets - * - QUICK: Fast analysis with sampling - * - TARGETED: Analysis for specific asset types/groups - */ - @NotBlank(message = "Analysis type is required") - private String analysisType; - - /** - * Optional scope limitations for targeted analysis. - * Can include asset types, data sources, or specific asset IDs. - */ - private PolicyImpactScopeDTO scope; - - /** - * Include estimated performance impact in the analysis. - * When true, includes estimates for processing time and resource usage. - */ - private Boolean includePerformanceEstimate = false; - - /** - * Include cost impact analysis in the preview. - * When true, estimates potential costs of policy enforcement. - */ - private Boolean includeCostImpact = false; - - /** - * Include compliance impact analysis. - * When true, shows how this policy affects compliance with other policies. - */ - private Boolean includeComplianceImpact = false; - - /** - * Additional context parameters for policy evaluation. - * Flexible map for custom analysis parameters. - */ - private Map contextParameters; - - // Constructors - public PolicyImpactRequestDTO() {} - - public PolicyImpactRequestDTO(String rulesContent, String analysisType) { - this.rulesContent = rulesContent; - this.analysisType = analysisType; - } - - // Getters and Setters - public String getRulesContent() { - return rulesContent; - } - - public void setRulesContent(String rulesContent) { - this.rulesContent = rulesContent; - } - - public String getAnalysisType() { - return analysisType; - } - - public void setAnalysisType(String analysisType) { - this.analysisType = analysisType; - } - - public PolicyImpactScopeDTO getScope() { - return scope; - } - - public void setScope(PolicyImpactScopeDTO scope) { - this.scope = scope; - } - - public Boolean getIncludePerformanceEstimate() { - return includePerformanceEstimate; - } - - public void setIncludePerformanceEstimate(Boolean includePerformanceEstimate) { - this.includePerformanceEstimate = includePerformanceEstimate; - } - - public Boolean getIncludeCostImpact() { - return includeCostImpact; - } - - public void setIncludeCostImpact(Boolean includeCostImpact) { - this.includeCostImpact = includeCostImpact; - } - - public Boolean getIncludeComplianceImpact() { - return includeComplianceImpact; - } - - public void setIncludeComplianceImpact(Boolean includeComplianceImpact) { - this.includeComplianceImpact = includeComplianceImpact; - } - - public Map getContextParameters() { - return contextParameters; - } - - public void setContextParameters(Map contextParameters) { - this.contextParameters = contextParameters; - } - - /** - * Nested DTO for defining analysis scope limitations. - */ - public static class PolicyImpactScopeDTO { - /** - * Specific asset types to include in analysis (e.g., "database", "file", "api"). - */ - private List assetTypes; - - /** - * Specific data sources to include (e.g., connection IDs or source names). - */ - private List dataSources; - - /** - * Specific asset IDs to analyze (for targeted analysis). - */ - private List assetIds; - - /** - * Include only assets with specific labels. - */ - private List requiredLabels; - - /** - * Exclude assets with specific labels. - */ - private List excludedLabels; - - /** - * Maximum number of assets to analyze (for performance control). - */ - private Integer maxAssets; - - // Constructors - public PolicyImpactScopeDTO() {} - - // Getters and Setters - public List getAssetTypes() { - return assetTypes; - } - - public void setAssetTypes(List assetTypes) { - this.assetTypes = assetTypes; - } - - public List getDataSources() { - return dataSources; - } - - public void setDataSources(List dataSources) { - this.dataSources = dataSources; - } - - public List getAssetIds() { - return assetIds; - } - - public void setAssetIds(List assetIds) { - this.assetIds = assetIds; - } - - public List getRequiredLabels() { - return requiredLabels; - } - - public void setRequiredLabels(List requiredLabels) { - this.requiredLabels = requiredLabels; - } - - public List getExcludedLabels() { - return excludedLabels; - } - - public void setExcludedLabels(List excludedLabels) { - this.excludedLabels = excludedLabels; - } - - public Integer getMaxAssets() { - return maxAssets; - } - - public void setMaxAssets(Integer maxAssets) { - this.maxAssets = maxAssets; - } - } +package com.dalab.policyengine.dto; + +import java.util.List; +import java.util.Map; + +import jakarta.validation.constraints.NotBlank; + +/** + * DTO for requesting policy impact analysis preview. + * This allows users to see what assets would be affected by policy changes before implementation. + */ +public class PolicyImpactRequestDTO { + + /** + * The policy rules content to analyze for impact (JSON format). + * Required field containing the policy rules that need impact analysis. + */ + @NotBlank(message = "Policy rules content is required") + private String rulesContent; + + /** + * Type of analysis to perform: FULL, QUICK, or TARGETED. + * - FULL: Comprehensive analysis across all assets + * - QUICK: Fast analysis with sampling + * - TARGETED: Analysis for specific asset types/groups + */ + @NotBlank(message = "Analysis type is required") + private String analysisType; + + /** + * Optional scope limitations for targeted analysis. + * Can include asset types, data sources, or specific asset IDs. + */ + private PolicyImpactScopeDTO scope; + + /** + * Include estimated performance impact in the analysis. + * When true, includes estimates for processing time and resource usage. + */ + private Boolean includePerformanceEstimate = false; + + /** + * Include cost impact analysis in the preview. + * When true, estimates potential costs of policy enforcement. + */ + private Boolean includeCostImpact = false; + + /** + * Include compliance impact analysis. + * When true, shows how this policy affects compliance with other policies. + */ + private Boolean includeComplianceImpact = false; + + /** + * Additional context parameters for policy evaluation. + * Flexible map for custom analysis parameters. + */ + private Map contextParameters; + + // Constructors + public PolicyImpactRequestDTO() {} + + public PolicyImpactRequestDTO(String rulesContent, String analysisType) { + this.rulesContent = rulesContent; + this.analysisType = analysisType; + } + + // Getters and Setters + public String getRulesContent() { + return rulesContent; + } + + public void setRulesContent(String rulesContent) { + this.rulesContent = rulesContent; + } + + public String getAnalysisType() { + return analysisType; + } + + public void setAnalysisType(String analysisType) { + this.analysisType = analysisType; + } + + public PolicyImpactScopeDTO getScope() { + return scope; + } + + public void setScope(PolicyImpactScopeDTO scope) { + this.scope = scope; + } + + public Boolean getIncludePerformanceEstimate() { + return includePerformanceEstimate; + } + + public void setIncludePerformanceEstimate(Boolean includePerformanceEstimate) { + this.includePerformanceEstimate = includePerformanceEstimate; + } + + public Boolean getIncludeCostImpact() { + return includeCostImpact; + } + + public void setIncludeCostImpact(Boolean includeCostImpact) { + this.includeCostImpact = includeCostImpact; + } + + public Boolean getIncludeComplianceImpact() { + return includeComplianceImpact; + } + + public void setIncludeComplianceImpact(Boolean includeComplianceImpact) { + this.includeComplianceImpact = includeComplianceImpact; + } + + public Map getContextParameters() { + return contextParameters; + } + + public void setContextParameters(Map contextParameters) { + this.contextParameters = contextParameters; + } + + /** + * Nested DTO for defining analysis scope limitations. + */ + public static class PolicyImpactScopeDTO { + /** + * Specific asset types to include in analysis (e.g., "database", "file", "api"). + */ + private List assetTypes; + + /** + * Specific data sources to include (e.g., connection IDs or source names). + */ + private List dataSources; + + /** + * Specific asset IDs to analyze (for targeted analysis). + */ + private List assetIds; + + /** + * Include only assets with specific labels. + */ + private List requiredLabels; + + /** + * Exclude assets with specific labels. + */ + private List excludedLabels; + + /** + * Maximum number of assets to analyze (for performance control). + */ + private Integer maxAssets; + + // Constructors + public PolicyImpactScopeDTO() {} + + // Getters and Setters + public List getAssetTypes() { + return assetTypes; + } + + public void setAssetTypes(List assetTypes) { + this.assetTypes = assetTypes; + } + + public List getDataSources() { + return dataSources; + } + + public void setDataSources(List dataSources) { + this.dataSources = dataSources; + } + + public List getAssetIds() { + return assetIds; + } + + public void setAssetIds(List assetIds) { + this.assetIds = assetIds; + } + + public List getRequiredLabels() { + return requiredLabels; + } + + public void setRequiredLabels(List requiredLabels) { + this.requiredLabels = requiredLabels; + } + + public List getExcludedLabels() { + return excludedLabels; + } + + public void setExcludedLabels(List excludedLabels) { + this.excludedLabels = excludedLabels; + } + + public Integer getMaxAssets() { + return maxAssets; + } + + public void setMaxAssets(Integer maxAssets) { + this.maxAssets = maxAssets; + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyImpactResponseDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyImpactResponseDTO.java index 1b07eb14e66fc0d1f6293291d079bd351870bde1..7cc8fbc7f727b15646338b8345aa15d107dce2c7 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyImpactResponseDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyImpactResponseDTO.java @@ -1,376 +1,376 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.List; -import java.util.Map; - -/** - * Comprehensive DTO for policy impact analysis response. - * Contains detailed analysis of what assets would be affected by policy changes. - */ -public class PolicyImpactResponseDTO { - - /** - * Unique identifier for this impact analysis. - */ - private String analysisId; - - /** - * Timestamp when the analysis was performed. - */ - private Instant analyzedAt; - - /** - * Type of analysis performed: FULL, QUICK, or TARGETED. - */ - private String analysisType; - - /** - * Overall impact summary with key metrics. - */ - private ImpactSummaryDTO summary; - - /** - * Detailed breakdown of affected assets by category. - */ - private List affectedAssets; - - /** - * Performance impact estimates (if requested). - */ - private PerformanceImpactDTO performanceImpact; - - /** - * Cost impact analysis (if requested). - */ - private CostImpactDTO costImpact; - - /** - * Compliance impact analysis (if requested). - */ - private ComplianceImpactDTO complianceImpact; - - /** - * Risk assessment for implementing this policy. - */ - private RiskAssessmentDTO riskAssessment; - - /** - * Recommendations for policy implementation. - */ - private List recommendations; - - /** - * Analysis execution metadata. - */ - private AnalysisMetadataDTO metadata; - - // Constructors - public PolicyImpactResponseDTO() {} - - public PolicyImpactResponseDTO(String analysisId, String analysisType) { - this.analysisId = analysisId; - this.analysisType = analysisType; - this.analyzedAt = Instant.now(); - } - - // Getters and Setters - public String getAnalysisId() { - return analysisId; - } - - public void setAnalysisId(String analysisId) { - this.analysisId = analysisId; - } - - public Instant getAnalyzedAt() { - return analyzedAt; - } - - public void setAnalyzedAt(Instant analyzedAt) { - this.analyzedAt = analyzedAt; - } - - public String getAnalysisType() { - return analysisType; - } - - public void setAnalysisType(String analysisType) { - this.analysisType = analysisType; - } - - public ImpactSummaryDTO getSummary() { - return summary; - } - - public void setSummary(ImpactSummaryDTO summary) { - this.summary = summary; - } - - public List getAffectedAssets() { - return affectedAssets; - } - - public void setAffectedAssets(List affectedAssets) { - this.affectedAssets = affectedAssets; - } - - public PerformanceImpactDTO getPerformanceImpact() { - return performanceImpact; - } - - public void setPerformanceImpact(PerformanceImpactDTO performanceImpact) { - this.performanceImpact = performanceImpact; - } - - public CostImpactDTO getCostImpact() { - return costImpact; - } - - public void setCostImpact(CostImpactDTO costImpact) { - this.costImpact = costImpact; - } - - public ComplianceImpactDTO getComplianceImpact() { - return complianceImpact; - } - - public void setComplianceImpact(ComplianceImpactDTO complianceImpact) { - this.complianceImpact = complianceImpact; - } - - public RiskAssessmentDTO getRiskAssessment() { - return riskAssessment; - } - - public void setRiskAssessment(RiskAssessmentDTO riskAssessment) { - this.riskAssessment = riskAssessment; - } - - public List getRecommendations() { - return recommendations; - } - - public void setRecommendations(List recommendations) { - this.recommendations = recommendations; - } - - public AnalysisMetadataDTO getMetadata() { - return metadata; - } - - public void setMetadata(AnalysisMetadataDTO metadata) { - this.metadata = metadata; - } - - /** - * Overall impact summary with key metrics. - */ - public static class ImpactSummaryDTO { - private Integer totalAssetsAnalyzed; - private Integer totalAssetsAffected; - private Integer highImpactAssets; - private Integer mediumImpactAssets; - private Integer lowImpactAssets; - private String overallRiskLevel; // LOW, MEDIUM, HIGH, CRITICAL - private Double impactPercentage; - - // Constructors, getters, and setters - public ImpactSummaryDTO() {} - - public Integer getTotalAssetsAnalyzed() { return totalAssetsAnalyzed; } - public void setTotalAssetsAnalyzed(Integer totalAssetsAnalyzed) { this.totalAssetsAnalyzed = totalAssetsAnalyzed; } - - public Integer getTotalAssetsAffected() { return totalAssetsAffected; } - public void setTotalAssetsAffected(Integer totalAssetsAffected) { this.totalAssetsAffected = totalAssetsAffected; } - - public Integer getHighImpactAssets() { return highImpactAssets; } - public void setHighImpactAssets(Integer highImpactAssets) { this.highImpactAssets = highImpactAssets; } - - public Integer getMediumImpactAssets() { return mediumImpactAssets; } - public void setMediumImpactAssets(Integer mediumImpactAssets) { this.mediumImpactAssets = mediumImpactAssets; } - - public Integer getLowImpactAssets() { return lowImpactAssets; } - public void setLowImpactAssets(Integer lowImpactAssets) { this.lowImpactAssets = lowImpactAssets; } - - public String getOverallRiskLevel() { return overallRiskLevel; } - public void setOverallRiskLevel(String overallRiskLevel) { this.overallRiskLevel = overallRiskLevel; } - - public Double getImpactPercentage() { return impactPercentage; } - public void setImpactPercentage(Double impactPercentage) { this.impactPercentage = impactPercentage; } - } - - /** - * Individual asset impact details. - */ - public static class AssetImpactDTO { - private String assetId; - private String assetName; - private String assetType; - private String impactLevel; // HIGH, MEDIUM, LOW - private List affectedAttributes; - private List appliedActions; - private String riskAssessment; - private Map impactDetails; - - // Constructors, getters, and setters - public AssetImpactDTO() {} - - public String getAssetId() { return assetId; } - public void setAssetId(String assetId) { this.assetId = assetId; } - - public String getAssetName() { return assetName; } - public void setAssetName(String assetName) { this.assetName = assetName; } - - public String getAssetType() { return assetType; } - public void setAssetType(String assetType) { this.assetType = assetType; } - - public String getImpactLevel() { return impactLevel; } - public void setImpactLevel(String impactLevel) { this.impactLevel = impactLevel; } - - public List getAffectedAttributes() { return affectedAttributes; } - public void setAffectedAttributes(List affectedAttributes) { this.affectedAttributes = affectedAttributes; } - - public List getAppliedActions() { return appliedActions; } - public void setAppliedActions(List appliedActions) { this.appliedActions = appliedActions; } - - public String getRiskAssessment() { return riskAssessment; } - public void setRiskAssessment(String riskAssessment) { this.riskAssessment = riskAssessment; } - - public Map getImpactDetails() { return impactDetails; } - public void setImpactDetails(Map impactDetails) { this.impactDetails = impactDetails; } - } - - /** - * Performance impact estimates. - */ - public static class PerformanceImpactDTO { - private Long estimatedProcessingTimeMs; - private Double cpuUtilizationIncrease; - private Double memoryUtilizationIncrease; - private Integer estimatedApiCalls; - private String performanceRiskLevel; - - // Constructors, getters, and setters - public PerformanceImpactDTO() {} - - public Long getEstimatedProcessingTimeMs() { return estimatedProcessingTimeMs; } - public void setEstimatedProcessingTimeMs(Long estimatedProcessingTimeMs) { this.estimatedProcessingTimeMs = estimatedProcessingTimeMs; } - - public Double getCpuUtilizationIncrease() { return cpuUtilizationIncrease; } - public void setCpuUtilizationIncrease(Double cpuUtilizationIncrease) { this.cpuUtilizationIncrease = cpuUtilizationIncrease; } - - public Double getMemoryUtilizationIncrease() { return memoryUtilizationIncrease; } - public void setMemoryUtilizationIncrease(Double memoryUtilizationIncrease) { this.memoryUtilizationIncrease = memoryUtilizationIncrease; } - - public Integer getEstimatedApiCalls() { return estimatedApiCalls; } - public void setEstimatedApiCalls(Integer estimatedApiCalls) { this.estimatedApiCalls = estimatedApiCalls; } - - public String getPerformanceRiskLevel() { return performanceRiskLevel; } - public void setPerformanceRiskLevel(String performanceRiskLevel) { this.performanceRiskLevel = performanceRiskLevel; } - } - - /** - * Cost impact analysis. - */ - public static class CostImpactDTO { - private Double estimatedMonthlyCost; - private Double estimatedImplementationCost; - private Double potentialSavings; - private String costRiskLevel; - private Map costBreakdown; - - // Constructors, getters, and setters - public CostImpactDTO() {} - - public Double getEstimatedMonthlyCost() { return estimatedMonthlyCost; } - public void setEstimatedMonthlyCost(Double estimatedMonthlyCost) { this.estimatedMonthlyCost = estimatedMonthlyCost; } - - public Double getEstimatedImplementationCost() { return estimatedImplementationCost; } - public void setEstimatedImplementationCost(Double estimatedImplementationCost) { this.estimatedImplementationCost = estimatedImplementationCost; } - - public Double getPotentialSavings() { return potentialSavings; } - public void setPotentialSavings(Double potentialSavings) { this.potentialSavings = potentialSavings; } - - public String getCostRiskLevel() { return costRiskLevel; } - public void setCostRiskLevel(String costRiskLevel) { this.costRiskLevel = costRiskLevel; } - - public Map getCostBreakdown() { return costBreakdown; } - public void setCostBreakdown(Map costBreakdown) { this.costBreakdown = costBreakdown; } - } - - /** - * Compliance impact analysis. - */ - public static class ComplianceImpactDTO { - private Integer conflictingPolicies; - private List complianceFrameworksAffected; - private String complianceRiskLevel; - private List potentialViolations; - - // Constructors, getters, and setters - public ComplianceImpactDTO() {} - - public Integer getConflictingPolicies() { return conflictingPolicies; } - public void setConflictingPolicies(Integer conflictingPolicies) { this.conflictingPolicies = conflictingPolicies; } - - public List getComplianceFrameworksAffected() { return complianceFrameworksAffected; } - public void setComplianceFrameworksAffected(List complianceFrameworksAffected) { this.complianceFrameworksAffected = complianceFrameworksAffected; } - - public String getComplianceRiskLevel() { return complianceRiskLevel; } - public void setComplianceRiskLevel(String complianceRiskLevel) { this.complianceRiskLevel = complianceRiskLevel; } - - public List getPotentialViolations() { return potentialViolations; } - public void setPotentialViolations(List potentialViolations) { this.potentialViolations = potentialViolations; } - } - - /** - * Risk assessment for policy implementation. - */ - public static class RiskAssessmentDTO { - private String overallRiskLevel; - private List identifiedRisks; - private List mitigationStrategies; - private Double riskScore; - - // Constructors, getters, and setters - public RiskAssessmentDTO() {} - - public String getOverallRiskLevel() { return overallRiskLevel; } - public void setOverallRiskLevel(String overallRiskLevel) { this.overallRiskLevel = overallRiskLevel; } - - public List getIdentifiedRisks() { return identifiedRisks; } - public void setIdentifiedRisks(List identifiedRisks) { this.identifiedRisks = identifiedRisks; } - - public List getMitigationStrategies() { return mitigationStrategies; } - public void setMitigationStrategies(List mitigationStrategies) { this.mitigationStrategies = mitigationStrategies; } - - public Double getRiskScore() { return riskScore; } - public void setRiskScore(Double riskScore) { this.riskScore = riskScore; } - } - - /** - * Analysis execution metadata. - */ - public static class AnalysisMetadataDTO { - private Long executionTimeMs; - private String analysisVersion; - private Boolean usesCachedData; - private Instant dataFreshnessTimestamp; - - // Constructors, getters, and setters - public AnalysisMetadataDTO() {} - - public Long getExecutionTimeMs() { return executionTimeMs; } - public void setExecutionTimeMs(Long executionTimeMs) { this.executionTimeMs = executionTimeMs; } - - public String getAnalysisVersion() { return analysisVersion; } - public void setAnalysisVersion(String analysisVersion) { this.analysisVersion = analysisVersion; } - - public Boolean getUsesCachedData() { return usesCachedData; } - public void setUsesCachedData(Boolean usesCachedData) { this.usesCachedData = usesCachedData; } - - public Instant getDataFreshnessTimestamp() { return dataFreshnessTimestamp; } - public void setDataFreshnessTimestamp(Instant dataFreshnessTimestamp) { this.dataFreshnessTimestamp = dataFreshnessTimestamp; } - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +/** + * Comprehensive DTO for policy impact analysis response. + * Contains detailed analysis of what assets would be affected by policy changes. + */ +public class PolicyImpactResponseDTO { + + /** + * Unique identifier for this impact analysis. + */ + private String analysisId; + + /** + * Timestamp when the analysis was performed. + */ + private Instant analyzedAt; + + /** + * Type of analysis performed: FULL, QUICK, or TARGETED. + */ + private String analysisType; + + /** + * Overall impact summary with key metrics. + */ + private ImpactSummaryDTO summary; + + /** + * Detailed breakdown of affected assets by category. + */ + private List affectedAssets; + + /** + * Performance impact estimates (if requested). + */ + private PerformanceImpactDTO performanceImpact; + + /** + * Cost impact analysis (if requested). + */ + private CostImpactDTO costImpact; + + /** + * Compliance impact analysis (if requested). + */ + private ComplianceImpactDTO complianceImpact; + + /** + * Risk assessment for implementing this policy. + */ + private RiskAssessmentDTO riskAssessment; + + /** + * Recommendations for policy implementation. + */ + private List recommendations; + + /** + * Analysis execution metadata. + */ + private AnalysisMetadataDTO metadata; + + // Constructors + public PolicyImpactResponseDTO() {} + + public PolicyImpactResponseDTO(String analysisId, String analysisType) { + this.analysisId = analysisId; + this.analysisType = analysisType; + this.analyzedAt = Instant.now(); + } + + // Getters and Setters + public String getAnalysisId() { + return analysisId; + } + + public void setAnalysisId(String analysisId) { + this.analysisId = analysisId; + } + + public Instant getAnalyzedAt() { + return analyzedAt; + } + + public void setAnalyzedAt(Instant analyzedAt) { + this.analyzedAt = analyzedAt; + } + + public String getAnalysisType() { + return analysisType; + } + + public void setAnalysisType(String analysisType) { + this.analysisType = analysisType; + } + + public ImpactSummaryDTO getSummary() { + return summary; + } + + public void setSummary(ImpactSummaryDTO summary) { + this.summary = summary; + } + + public List getAffectedAssets() { + return affectedAssets; + } + + public void setAffectedAssets(List affectedAssets) { + this.affectedAssets = affectedAssets; + } + + public PerformanceImpactDTO getPerformanceImpact() { + return performanceImpact; + } + + public void setPerformanceImpact(PerformanceImpactDTO performanceImpact) { + this.performanceImpact = performanceImpact; + } + + public CostImpactDTO getCostImpact() { + return costImpact; + } + + public void setCostImpact(CostImpactDTO costImpact) { + this.costImpact = costImpact; + } + + public ComplianceImpactDTO getComplianceImpact() { + return complianceImpact; + } + + public void setComplianceImpact(ComplianceImpactDTO complianceImpact) { + this.complianceImpact = complianceImpact; + } + + public RiskAssessmentDTO getRiskAssessment() { + return riskAssessment; + } + + public void setRiskAssessment(RiskAssessmentDTO riskAssessment) { + this.riskAssessment = riskAssessment; + } + + public List getRecommendations() { + return recommendations; + } + + public void setRecommendations(List recommendations) { + this.recommendations = recommendations; + } + + public AnalysisMetadataDTO getMetadata() { + return metadata; + } + + public void setMetadata(AnalysisMetadataDTO metadata) { + this.metadata = metadata; + } + + /** + * Overall impact summary with key metrics. + */ + public static class ImpactSummaryDTO { + private Integer totalAssetsAnalyzed; + private Integer totalAssetsAffected; + private Integer highImpactAssets; + private Integer mediumImpactAssets; + private Integer lowImpactAssets; + private String overallRiskLevel; // LOW, MEDIUM, HIGH, CRITICAL + private Double impactPercentage; + + // Constructors, getters, and setters + public ImpactSummaryDTO() {} + + public Integer getTotalAssetsAnalyzed() { return totalAssetsAnalyzed; } + public void setTotalAssetsAnalyzed(Integer totalAssetsAnalyzed) { this.totalAssetsAnalyzed = totalAssetsAnalyzed; } + + public Integer getTotalAssetsAffected() { return totalAssetsAffected; } + public void setTotalAssetsAffected(Integer totalAssetsAffected) { this.totalAssetsAffected = totalAssetsAffected; } + + public Integer getHighImpactAssets() { return highImpactAssets; } + public void setHighImpactAssets(Integer highImpactAssets) { this.highImpactAssets = highImpactAssets; } + + public Integer getMediumImpactAssets() { return mediumImpactAssets; } + public void setMediumImpactAssets(Integer mediumImpactAssets) { this.mediumImpactAssets = mediumImpactAssets; } + + public Integer getLowImpactAssets() { return lowImpactAssets; } + public void setLowImpactAssets(Integer lowImpactAssets) { this.lowImpactAssets = lowImpactAssets; } + + public String getOverallRiskLevel() { return overallRiskLevel; } + public void setOverallRiskLevel(String overallRiskLevel) { this.overallRiskLevel = overallRiskLevel; } + + public Double getImpactPercentage() { return impactPercentage; } + public void setImpactPercentage(Double impactPercentage) { this.impactPercentage = impactPercentage; } + } + + /** + * Individual asset impact details. + */ + public static class AssetImpactDTO { + private String assetId; + private String assetName; + private String assetType; + private String impactLevel; // HIGH, MEDIUM, LOW + private List affectedAttributes; + private List appliedActions; + private String riskAssessment; + private Map impactDetails; + + // Constructors, getters, and setters + public AssetImpactDTO() {} + + public String getAssetId() { return assetId; } + public void setAssetId(String assetId) { this.assetId = assetId; } + + public String getAssetName() { return assetName; } + public void setAssetName(String assetName) { this.assetName = assetName; } + + public String getAssetType() { return assetType; } + public void setAssetType(String assetType) { this.assetType = assetType; } + + public String getImpactLevel() { return impactLevel; } + public void setImpactLevel(String impactLevel) { this.impactLevel = impactLevel; } + + public List getAffectedAttributes() { return affectedAttributes; } + public void setAffectedAttributes(List affectedAttributes) { this.affectedAttributes = affectedAttributes; } + + public List getAppliedActions() { return appliedActions; } + public void setAppliedActions(List appliedActions) { this.appliedActions = appliedActions; } + + public String getRiskAssessment() { return riskAssessment; } + public void setRiskAssessment(String riskAssessment) { this.riskAssessment = riskAssessment; } + + public Map getImpactDetails() { return impactDetails; } + public void setImpactDetails(Map impactDetails) { this.impactDetails = impactDetails; } + } + + /** + * Performance impact estimates. + */ + public static class PerformanceImpactDTO { + private Long estimatedProcessingTimeMs; + private Double cpuUtilizationIncrease; + private Double memoryUtilizationIncrease; + private Integer estimatedApiCalls; + private String performanceRiskLevel; + + // Constructors, getters, and setters + public PerformanceImpactDTO() {} + + public Long getEstimatedProcessingTimeMs() { return estimatedProcessingTimeMs; } + public void setEstimatedProcessingTimeMs(Long estimatedProcessingTimeMs) { this.estimatedProcessingTimeMs = estimatedProcessingTimeMs; } + + public Double getCpuUtilizationIncrease() { return cpuUtilizationIncrease; } + public void setCpuUtilizationIncrease(Double cpuUtilizationIncrease) { this.cpuUtilizationIncrease = cpuUtilizationIncrease; } + + public Double getMemoryUtilizationIncrease() { return memoryUtilizationIncrease; } + public void setMemoryUtilizationIncrease(Double memoryUtilizationIncrease) { this.memoryUtilizationIncrease = memoryUtilizationIncrease; } + + public Integer getEstimatedApiCalls() { return estimatedApiCalls; } + public void setEstimatedApiCalls(Integer estimatedApiCalls) { this.estimatedApiCalls = estimatedApiCalls; } + + public String getPerformanceRiskLevel() { return performanceRiskLevel; } + public void setPerformanceRiskLevel(String performanceRiskLevel) { this.performanceRiskLevel = performanceRiskLevel; } + } + + /** + * Cost impact analysis. + */ + public static class CostImpactDTO { + private Double estimatedMonthlyCost; + private Double estimatedImplementationCost; + private Double potentialSavings; + private String costRiskLevel; + private Map costBreakdown; + + // Constructors, getters, and setters + public CostImpactDTO() {} + + public Double getEstimatedMonthlyCost() { return estimatedMonthlyCost; } + public void setEstimatedMonthlyCost(Double estimatedMonthlyCost) { this.estimatedMonthlyCost = estimatedMonthlyCost; } + + public Double getEstimatedImplementationCost() { return estimatedImplementationCost; } + public void setEstimatedImplementationCost(Double estimatedImplementationCost) { this.estimatedImplementationCost = estimatedImplementationCost; } + + public Double getPotentialSavings() { return potentialSavings; } + public void setPotentialSavings(Double potentialSavings) { this.potentialSavings = potentialSavings; } + + public String getCostRiskLevel() { return costRiskLevel; } + public void setCostRiskLevel(String costRiskLevel) { this.costRiskLevel = costRiskLevel; } + + public Map getCostBreakdown() { return costBreakdown; } + public void setCostBreakdown(Map costBreakdown) { this.costBreakdown = costBreakdown; } + } + + /** + * Compliance impact analysis. + */ + public static class ComplianceImpactDTO { + private Integer conflictingPolicies; + private List complianceFrameworksAffected; + private String complianceRiskLevel; + private List potentialViolations; + + // Constructors, getters, and setters + public ComplianceImpactDTO() {} + + public Integer getConflictingPolicies() { return conflictingPolicies; } + public void setConflictingPolicies(Integer conflictingPolicies) { this.conflictingPolicies = conflictingPolicies; } + + public List getComplianceFrameworksAffected() { return complianceFrameworksAffected; } + public void setComplianceFrameworksAffected(List complianceFrameworksAffected) { this.complianceFrameworksAffected = complianceFrameworksAffected; } + + public String getComplianceRiskLevel() { return complianceRiskLevel; } + public void setComplianceRiskLevel(String complianceRiskLevel) { this.complianceRiskLevel = complianceRiskLevel; } + + public List getPotentialViolations() { return potentialViolations; } + public void setPotentialViolations(List potentialViolations) { this.potentialViolations = potentialViolations; } + } + + /** + * Risk assessment for policy implementation. + */ + public static class RiskAssessmentDTO { + private String overallRiskLevel; + private List identifiedRisks; + private List mitigationStrategies; + private Double riskScore; + + // Constructors, getters, and setters + public RiskAssessmentDTO() {} + + public String getOverallRiskLevel() { return overallRiskLevel; } + public void setOverallRiskLevel(String overallRiskLevel) { this.overallRiskLevel = overallRiskLevel; } + + public List getIdentifiedRisks() { return identifiedRisks; } + public void setIdentifiedRisks(List identifiedRisks) { this.identifiedRisks = identifiedRisks; } + + public List getMitigationStrategies() { return mitigationStrategies; } + public void setMitigationStrategies(List mitigationStrategies) { this.mitigationStrategies = mitigationStrategies; } + + public Double getRiskScore() { return riskScore; } + public void setRiskScore(Double riskScore) { this.riskScore = riskScore; } + } + + /** + * Analysis execution metadata. + */ + public static class AnalysisMetadataDTO { + private Long executionTimeMs; + private String analysisVersion; + private Boolean usesCachedData; + private Instant dataFreshnessTimestamp; + + // Constructors, getters, and setters + public AnalysisMetadataDTO() {} + + public Long getExecutionTimeMs() { return executionTimeMs; } + public void setExecutionTimeMs(Long executionTimeMs) { this.executionTimeMs = executionTimeMs; } + + public String getAnalysisVersion() { return analysisVersion; } + public void setAnalysisVersion(String analysisVersion) { this.analysisVersion = analysisVersion; } + + public Boolean getUsesCachedData() { return usesCachedData; } + public void setUsesCachedData(Boolean usesCachedData) { this.usesCachedData = usesCachedData; } + + public Instant getDataFreshnessTimestamp() { return dataFreshnessTimestamp; } + public void setDataFreshnessTimestamp(Instant dataFreshnessTimestamp) { this.dataFreshnessTimestamp = dataFreshnessTimestamp; } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyInputDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyInputDTO.java index e4c3ba77f10b8f123db0447c2a50a3e3447c87f5..e2ff4d28898c17a65734a446dc6eedfba7d37a00 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyInputDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyInputDTO.java @@ -1,80 +1,80 @@ -package com.dalab.policyengine.dto; - -import java.util.List; -import java.util.Map; - -import com.dalab.policyengine.model.PolicyStatus; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Size; - -public class PolicyInputDTO { - - @NotBlank - @Size(max = 255) - private String name; - - @Size(max = 1000) - private String description; - - private PolicyStatus status = PolicyStatus.DISABLED; - - private String conditionLogic; // e.g., "rule1 && (rule2 || rule3)" - - @NotEmpty - @Valid - private List rules; - - private Map actions; // Policy-level actions - - // Getters and Setters - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public PolicyStatus getStatus() { - return status; - } - - public void setStatus(PolicyStatus status) { - this.status = status; - } - - public String getConditionLogic() { - return conditionLogic; - } - - public void setConditionLogic(String conditionLogic) { - this.conditionLogic = conditionLogic; - } - - public List getRules() { - return rules; - } - - public void setRules(List rules) { - this.rules = rules; - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } +package com.dalab.policyengine.dto; + +import java.util.List; +import java.util.Map; + +import com.dalab.policyengine.model.PolicyStatus; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; + +public class PolicyInputDTO { + + @NotBlank + @Size(max = 255) + private String name; + + @Size(max = 1000) + private String description; + + private PolicyStatus status = PolicyStatus.DISABLED; + + private String conditionLogic; // e.g., "rule1 && (rule2 || rule3)" + + @NotEmpty + @Valid + private List rules; + + private Map actions; // Policy-level actions + + // Getters and Setters + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PolicyStatus getStatus() { + return status; + } + + public void setStatus(PolicyStatus status) { + this.status = status; + } + + public String getConditionLogic() { + return conditionLogic; + } + + public void setConditionLogic(String conditionLogic) { + this.conditionLogic = conditionLogic; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyOutputDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyOutputDTO.java index 166c1c86bafbe44d54eaa6b71f36d55231461be7..17d08665a26aa84f0c3b40b57657dcc70e015336 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyOutputDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyOutputDTO.java @@ -1,111 +1,111 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import com.dalab.policyengine.model.PolicyStatus; - -public class PolicyOutputDTO { - private UUID id; - private String name; - private String description; - private PolicyStatus status; - private String conditionLogic; - private List rules; - private Map actions; - private Instant createdAt; - private Instant updatedAt; - private UUID createdByUserId; - private UUID updatedByUserId; - - // Getters and Setters - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public PolicyStatus getStatus() { - return status; - } - - public void setStatus(PolicyStatus status) { - this.status = status; - } - - public String getConditionLogic() { - return conditionLogic; - } - - public void setConditionLogic(String conditionLogic) { - this.conditionLogic = conditionLogic; - } - - public List getRules() { - return rules; - } - - public void setRules(List rules) { - this.rules = rules; - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.dalab.policyengine.model.PolicyStatus; + +public class PolicyOutputDTO { + private UUID id; + private String name; + private String description; + private PolicyStatus status; + private String conditionLogic; + private List rules; + private Map actions; + private Instant createdAt; + private Instant updatedAt; + private UUID createdByUserId; + private UUID updatedByUserId; + + // Getters and Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PolicyStatus getStatus() { + return status; + } + + public void setStatus(PolicyStatus status) { + this.status = status; + } + + public String getConditionLogic() { + return conditionLogic; + } + + public void setConditionLogic(String conditionLogic) { + this.conditionLogic = conditionLogic; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/dto/PolicyRuleDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicyRuleDTO.java index 0b43a59c8b3f02a9e92c4c89815395b7c0b90514..a5bc9dc4f6ee709bed2104e254a177884d8c8e6a 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicyRuleDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicyRuleDTO.java @@ -1,72 +1,72 @@ -package com.dalab.policyengine.dto; - -import java.util.Map; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -public class PolicyRuleDTO { - private String id; // UUID as String, can be null for new rules in create/update - - @NotBlank - @Size(max = 255) - private String name; - - @Size(max = 1000) - private String description; - - @NotBlank - private String condition; // MVEL expression - - private int priority = 1; - private Map actions; // Optional rule-specific actions - - // Getters and Setters - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getCondition() { - return condition; - } - - public void setCondition(String condition) { - this.condition = condition; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } -} +package com.dalab.policyengine.dto; + +import java.util.Map; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class PolicyRuleDTO { + private String id; // UUID as String, can be null for new rules in create/update + + @NotBlank + @Size(max = 255) + private String name; + + @Size(max = 1000) + private String description; + + @NotBlank + private String condition; // MVEL expression + + private int priority = 1; + private Map actions; // Optional rule-specific actions + + // Getters and Setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } +} diff --git a/src/main/java/com/dalab/policyengine/dto/PolicySummaryDTO.java b/src/main/java/com/dalab/policyengine/dto/PolicySummaryDTO.java index 656f73fc0e877fe103362f7796eca78428b0e8d2..edd6d73d64cf0f30f95b0c1d3036ab79e555b193 100644 --- a/src/main/java/com/dalab/policyengine/dto/PolicySummaryDTO.java +++ b/src/main/java/com/dalab/policyengine/dto/PolicySummaryDTO.java @@ -1,73 +1,73 @@ -package com.dalab.policyengine.dto; - -import java.time.Instant; -import java.util.UUID; - -import com.dalab.policyengine.model.PolicyStatus; - -public class PolicySummaryDTO { - private UUID id; - private String name; - private String description; - private PolicyStatus status; - private int ruleCount; - private Instant createdAt; - private Instant updatedAt; - - // Getters and Setters - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public PolicyStatus getStatus() { - return status; - } - - public void setStatus(PolicyStatus status) { - this.status = status; - } - - public int getRuleCount() { - return ruleCount; - } - - public void setRuleCount(int ruleCount) { - this.ruleCount = ruleCount; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } +package com.dalab.policyengine.dto; + +import java.time.Instant; +import java.util.UUID; + +import com.dalab.policyengine.model.PolicyStatus; + +public class PolicySummaryDTO { + private UUID id; + private String name; + private String description; + private PolicyStatus status; + private int ruleCount; + private Instant createdAt; + private Instant updatedAt; + + // Getters and Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PolicyStatus getStatus() { + return status; + } + + public void setStatus(PolicyStatus status) { + this.status = status; + } + + public int getRuleCount() { + return ruleCount; + } + + public void setRuleCount(int ruleCount) { + this.ruleCount = ruleCount; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/event/PolicyActionEvent.java b/src/main/java/com/dalab/policyengine/event/PolicyActionEvent.java index ac9227561f6e8f39c5c9e4ebacb05ff1fd7e4c22..3ca6c20d5a3c04442222abb56e0657700bba1f8b 100644 --- a/src/main/java/com/dalab/policyengine/event/PolicyActionEvent.java +++ b/src/main/java/com/dalab/policyengine/event/PolicyActionEvent.java @@ -1,102 +1,102 @@ -package com.dalab.policyengine.event; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -// This DTO represents an action triggered by a policy evaluation, -// to be published to a Kafka topic. -public class PolicyActionEvent { - private UUID eventId; - private String policyId; - private String policyName; - private String targetAssetId; - private String actionType; // e.g., "ADD_LABEL", "NOTIFY_EMAIL", "ARCHIVE_ASSET" - private Map actionParameters; // Parameters for the action, e.g., {"labelName": "PII", "confidence": 0.9} or {"to": "user@example.com", "subject": "Alert"} - private Instant timestamp; - private UUID triggeredByEvaluationId; // Link back to the PolicyEvaluation record - - public PolicyActionEvent() { - this.eventId = UUID.randomUUID(); - this.timestamp = Instant.now(); - } - - // Getters and Setters - public UUID getEventId() { - return eventId; - } - - public void setEventId(UUID eventId) { - this.eventId = eventId; - } - - public String getPolicyId() { - return policyId; - } - - public void setPolicyId(String policyId) { - this.policyId = policyId; - } - - public String getPolicyName() { - return policyName; - } - - public void setPolicyName(String policyName) { - this.policyName = policyName; - } - - public String getTargetAssetId() { - return targetAssetId; - } - - public void setTargetAssetId(String targetAssetId) { - this.targetAssetId = targetAssetId; - } - - public String getActionType() { - return actionType; - } - - public void setActionType(String actionType) { - this.actionType = actionType; - } - - public Map getActionParameters() { - return actionParameters; - } - - public void setActionParameters(Map actionParameters) { - this.actionParameters = actionParameters; - } - - public Instant getTimestamp() { - return timestamp; - } - - public void setTimestamp(Instant timestamp) { - this.timestamp = timestamp; - } - - public UUID getTriggeredByEvaluationId() { - return triggeredByEvaluationId; - } - - public void setTriggeredByEvaluationId(UUID triggeredByEvaluationId) { - this.triggeredByEvaluationId = triggeredByEvaluationId; - } - - @Override - public String toString() { - return "PolicyActionEvent{" + - "eventId=" + eventId + - ", policyId='" + policyId + '\'' + - ", policyName='" + policyName + '\'' + - ", targetAssetId='" + targetAssetId + '\'' + - ", actionType='" + actionType + '\'' + - ", actionParameters=" + actionParameters + - ", timestamp=" + timestamp + - ", triggeredByEvaluationId=" + triggeredByEvaluationId + - '}'; - } +package com.dalab.policyengine.event; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +// This DTO represents an action triggered by a policy evaluation, +// to be published to a Kafka topic. +public class PolicyActionEvent { + private UUID eventId; + private String policyId; + private String policyName; + private String targetAssetId; + private String actionType; // e.g., "ADD_LABEL", "NOTIFY_EMAIL", "ARCHIVE_ASSET" + private Map actionParameters; // Parameters for the action, e.g., {"labelName": "PII", "confidence": 0.9} or {"to": "user@example.com", "subject": "Alert"} + private Instant timestamp; + private UUID triggeredByEvaluationId; // Link back to the PolicyEvaluation record + + public PolicyActionEvent() { + this.eventId = UUID.randomUUID(); + this.timestamp = Instant.now(); + } + + // Getters and Setters + public UUID getEventId() { + return eventId; + } + + public void setEventId(UUID eventId) { + this.eventId = eventId; + } + + public String getPolicyId() { + return policyId; + } + + public void setPolicyId(String policyId) { + this.policyId = policyId; + } + + public String getPolicyName() { + return policyName; + } + + public void setPolicyName(String policyName) { + this.policyName = policyName; + } + + public String getTargetAssetId() { + return targetAssetId; + } + + public void setTargetAssetId(String targetAssetId) { + this.targetAssetId = targetAssetId; + } + + public String getActionType() { + return actionType; + } + + public void setActionType(String actionType) { + this.actionType = actionType; + } + + public Map getActionParameters() { + return actionParameters; + } + + public void setActionParameters(Map actionParameters) { + this.actionParameters = actionParameters; + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public UUID getTriggeredByEvaluationId() { + return triggeredByEvaluationId; + } + + public void setTriggeredByEvaluationId(UUID triggeredByEvaluationId) { + this.triggeredByEvaluationId = triggeredByEvaluationId; + } + + @Override + public String toString() { + return "PolicyActionEvent{" + + "eventId=" + eventId + + ", policyId='" + policyId + '\'' + + ", policyName='" + policyName + '\'' + + ", targetAssetId='" + targetAssetId + '\'' + + ", actionType='" + actionType + '\'' + + ", actionParameters=" + actionParameters + + ", timestamp=" + timestamp + + ", triggeredByEvaluationId=" + triggeredByEvaluationId + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeConsumer.java b/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeConsumer.java index f947fabffe0b955e07119ed044692bed666512a8..2a39892c911c9e7f34cfb676d86bf1f48d246342 100644 --- a/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeConsumer.java +++ b/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeConsumer.java @@ -1,50 +1,50 @@ -package com.dalab.policyengine.kafka.consumer; - -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -// Import the common AssetChangeEvent from da-protos -import com.dalab.common.event.AssetChangeEvent; -import com.dalab.policyengine.service.IPolicyEvaluationService; - -@Component -public class AssetChangeConsumer { - - private static final Logger log = LoggerFactory.getLogger(AssetChangeConsumer.class); - // System UUID for actions triggered by Kafka consumer events - private static final UUID KAFKA_CONSUMER_USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000001"); - - private final IPolicyEvaluationService policyEvaluationService; - - @Autowired - public AssetChangeConsumer(IPolicyEvaluationService policyEvaluationService) { - this.policyEvaluationService = policyEvaluationService; - } - - // Update KafkaListener to consume the common AssetChangeEvent - @KafkaListener(topics = "${app.kafka.topic.asset-change-event:asset-change-events}", groupId = "${spring.kafka.consumer.group-id}") - public void handleAssetChangeEvent(@Payload AssetChangeEvent event) { - log.info("Received AssetChangeEvent: AssetId={}, EventType={}", event.getAssetId(), event.getEventType()); - - try { - if (event == null || event.getAssetId() == null || !StringUtils.hasText(event.getAssetId())) { - log.error("AssetChangeEvent is null or missing assetId. Skipping."); - return; - } - // The PolicyEvaluationService will now fetch active policies and iterate. - policyEvaluationService.evaluatePolicyForAssetInternal(event, KAFKA_CONSUMER_USER_ID); - - } catch (Exception e) { - log.error("Failed to process AssetChangeEvent for assetId {}: {}. Error: {}", - event != null && event.getAssetId() != null ? event.getAssetId() : "unknown", - e.getMessage(), e); - } - } +package com.dalab.policyengine.kafka.consumer; + +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +// Import the common AssetChangeEvent from da-protos +import com.dalab.common.event.AssetChangeEvent; +import com.dalab.policyengine.service.IPolicyEvaluationService; + +@Component +public class AssetChangeConsumer { + + private static final Logger log = LoggerFactory.getLogger(AssetChangeConsumer.class); + // System UUID for actions triggered by Kafka consumer events + private static final UUID KAFKA_CONSUMER_USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000001"); + + private final IPolicyEvaluationService policyEvaluationService; + + @Autowired + public AssetChangeConsumer(IPolicyEvaluationService policyEvaluationService) { + this.policyEvaluationService = policyEvaluationService; + } + + // Update KafkaListener to consume the common AssetChangeEvent + @KafkaListener(topics = "${app.kafka.topic.asset-change-event:asset-change-events}", groupId = "${spring.kafka.consumer.group-id}") + public void handleAssetChangeEvent(@Payload AssetChangeEvent event) { + log.info("Received AssetChangeEvent: AssetId={}, EventType={}", event.getAssetId(), event.getEventType()); + + try { + if (event == null || event.getAssetId() == null || !StringUtils.hasText(event.getAssetId())) { + log.error("AssetChangeEvent is null or missing assetId. Skipping."); + return; + } + // The PolicyEvaluationService will now fetch active policies and iterate. + policyEvaluationService.evaluatePolicyForAssetInternal(event, KAFKA_CONSUMER_USER_ID); + + } catch (Exception e) { + log.error("Failed to process AssetChangeEvent for assetId {}: {}. Error: {}", + event != null && event.getAssetId() != null ? event.getAssetId() : "unknown", + e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeEventListener.java b/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeEventListener.java index e7e25aebd0953fa1dd176e8fe03ce961b0a9ef66..b07b4f90ce8a3dccdb62f5709142313191c3719e 100644 --- a/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeEventListener.java +++ b/src/main/java/com/dalab/policyengine/kafka/consumer/AssetChangeEventListener.java @@ -1,61 +1,61 @@ -package com.dalab.policyengine.kafka.consumer; - -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.kafka.support.KafkaHeaders; -import org.springframework.messaging.handler.annotation.Header; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.stereotype.Component; - -import com.dalab.common.event.AssetChangeEvent; -import com.dalab.policyengine.service.IPolicyEvaluationService; - -@Component -public class AssetChangeEventListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(AssetChangeEventListener.class); - - private final IPolicyEvaluationService policyEvaluationService; - - public AssetChangeEventListener(IPolicyEvaluationService policyEvaluationService) { - this.policyEvaluationService = policyEvaluationService; - } - - @KafkaListener( - topics = "#{'${app.kafka.topic.asset-change-event}'}", - groupId = "#{'${spring.kafka.consumer.group-id}'}" - // containerFactory = "assetChangeKafkaListenerContainerFactory" // If using custom factory - ) - public void handleAssetChangeEvent( - @Payload AssetChangeEvent event, - @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, - @Header(KafkaHeaders.RECEIVED_PARTITION) int partition, - @Header(KafkaHeaders.OFFSET) long offset - ) { - LOGGER.info( - "Received AssetChangeEvent on topic: {}, partition: {}, offset: {}: Asset ID: {}, EventType: {}", - topic, partition, offset, event.getAssetId(), event.getEventType() - ); - - try { - // Trigger policy evaluation based on the asset change - UUID initiatorUuid = null; - if (event.getUserId() != null && !event.getUserId().isEmpty()) { - try { - initiatorUuid = UUID.fromString(event.getUserId()); - } catch (IllegalArgumentException e) { - LOGGER.warn("Could not parse userId '{}' from AssetChangeEvent to UUID. Proceeding with null initiator.", event.getUserId(), e); - } - } - policyEvaluationService.evaluatePolicyForAssetInternal(event, initiatorUuid); - LOGGER.debug("Successfully processed AssetChangeEvent for assetId: {}", event.getAssetId()); - } catch (Exception e) { - // TODO: Implement proper error handling and dead-letter queue (DLQ) strategy - LOGGER.error("Error processing AssetChangeEvent for assetId: {}. Error: {}", event.getAssetId(), e.getMessage(), e); - // Depending on the error, you might rethrow to trigger Kafka's error handlers, or send to DLQ - } - } +package com.dalab.policyengine.kafka.consumer; + +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +import com.dalab.common.event.AssetChangeEvent; +import com.dalab.policyengine.service.IPolicyEvaluationService; + +@Component +public class AssetChangeEventListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(AssetChangeEventListener.class); + + private final IPolicyEvaluationService policyEvaluationService; + + public AssetChangeEventListener(IPolicyEvaluationService policyEvaluationService) { + this.policyEvaluationService = policyEvaluationService; + } + + @KafkaListener( + topics = "#{'${app.kafka.topic.asset-change-event}'}", + groupId = "#{'${spring.kafka.consumer.group-id}'}" + // containerFactory = "assetChangeKafkaListenerContainerFactory" // If using custom factory + ) + public void handleAssetChangeEvent( + @Payload AssetChangeEvent event, + @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, + @Header(KafkaHeaders.RECEIVED_PARTITION) int partition, + @Header(KafkaHeaders.OFFSET) long offset + ) { + LOGGER.info( + "Received AssetChangeEvent on topic: {}, partition: {}, offset: {}: Asset ID: {}, EventType: {}", + topic, partition, offset, event.getAssetId(), event.getEventType() + ); + + try { + // Trigger policy evaluation based on the asset change + UUID initiatorUuid = null; + if (event.getUserId() != null && !event.getUserId().isEmpty()) { + try { + initiatorUuid = UUID.fromString(event.getUserId()); + } catch (IllegalArgumentException e) { + LOGGER.warn("Could not parse userId '{}' from AssetChangeEvent to UUID. Proceeding with null initiator.", event.getUserId(), e); + } + } + policyEvaluationService.evaluatePolicyForAssetInternal(event, initiatorUuid); + LOGGER.debug("Successfully processed AssetChangeEvent for assetId: {}", event.getAssetId()); + } catch (Exception e) { + // TODO: Implement proper error handling and dead-letter queue (DLQ) strategy + LOGGER.error("Error processing AssetChangeEvent for assetId: {}. Error: {}", event.getAssetId(), e.getMessage(), e); + // Depending on the error, you might rethrow to trigger Kafka's error handlers, or send to DLQ + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionKafkaProducer.java b/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionKafkaProducer.java index 6787402bf33c6d02aef2df344e87f4dcfdd0a427..0be02dcba1ffcbbf127e75c12632b42ea13e8adb 100644 --- a/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionKafkaProducer.java +++ b/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionKafkaProducer.java @@ -1,54 +1,54 @@ -package com.dalab.policyengine.kafka.producer; - -import java.util.concurrent.CompletableFuture; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.kafka.support.SendResult; -import org.springframework.stereotype.Component; - -import com.dalab.policyengine.event.PolicyActionEvent; - -@Component -public class PolicyActionKafkaProducer { - - private static final Logger LOGGER = LoggerFactory.getLogger(PolicyActionKafkaProducer.class); - - private final KafkaTemplate kafkaTemplate; - - @Value("${app.kafka.topic.policy-action-event}") - private String topicName; - - public PolicyActionKafkaProducer(KafkaTemplate kafkaTemplate) { - this.kafkaTemplate = kafkaTemplate; - } - - public void sendPolicyActionEvent(PolicyActionEvent event) { - LOGGER.info("Sending PolicyActionEvent with eventId: {} to topic: {}", event.getEventId(), topicName); - - String eventKey = event.getTargetAssetId(); - if (eventKey == null || eventKey.isEmpty()) { - eventKey = event.getPolicyId(); - } - final String finalEventKey = eventKey; - - CompletableFuture> future = kafkaTemplate.send(topicName, finalEventKey, event); - - future.whenComplete((result, ex) -> { - if (ex == null) { - LOGGER.info( - "Successfully sent PolicyActionEvent [eventId: {}, key: {}] with offset: {}", - event.getEventId(), finalEventKey, result.getRecordMetadata().offset() - ); - } else { - LOGGER.error( - "Failed to send PolicyActionEvent [eventId: {}, key: {}]: {}", - event.getEventId(), finalEventKey, ex.getMessage(), ex - ); - // TODO: Handle send failure (e.g., retry, DLQ, persistent store for later retry) - } - }); - } +package com.dalab.policyengine.kafka.producer; + +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.support.SendResult; +import org.springframework.stereotype.Component; + +import com.dalab.policyengine.event.PolicyActionEvent; + +@Component +public class PolicyActionKafkaProducer { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolicyActionKafkaProducer.class); + + private final KafkaTemplate kafkaTemplate; + + @Value("${app.kafka.topic.policy-action-event}") + private String topicName; + + public PolicyActionKafkaProducer(KafkaTemplate kafkaTemplate) { + this.kafkaTemplate = kafkaTemplate; + } + + public void sendPolicyActionEvent(PolicyActionEvent event) { + LOGGER.info("Sending PolicyActionEvent with eventId: {} to topic: {}", event.getEventId(), topicName); + + String eventKey = event.getTargetAssetId(); + if (eventKey == null || eventKey.isEmpty()) { + eventKey = event.getPolicyId(); + } + final String finalEventKey = eventKey; + + CompletableFuture> future = kafkaTemplate.send(topicName, finalEventKey, event); + + future.whenComplete((result, ex) -> { + if (ex == null) { + LOGGER.info( + "Successfully sent PolicyActionEvent [eventId: {}, key: {}] with offset: {}", + event.getEventId(), finalEventKey, result.getRecordMetadata().offset() + ); + } else { + LOGGER.error( + "Failed to send PolicyActionEvent [eventId: {}, key: {}]: {}", + event.getEventId(), finalEventKey, ex.getMessage(), ex + ); + // TODO: Handle send failure (e.g., retry, DLQ, persistent store for later retry) + } + }); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionProducer.java b/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionProducer.java index 7bf603abf125cafc7d3f8794484328d90773e05f..cb4e80a140d29ca79811959850ce1272642e0977 100644 --- a/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionProducer.java +++ b/src/main/java/com/dalab/policyengine/kafka/producer/PolicyActionProducer.java @@ -1,42 +1,42 @@ -package com.dalab.policyengine.kafka.producer; - -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.stereotype.Service; - -@Service -public class PolicyActionProducer { - - private static final Logger log = LoggerFactory.getLogger(PolicyActionProducer.class); - - @Value("${app.kafka.topic.policy-action-event:policy-action-events}") - private String policyActionEventTopic; - - // Use generic KafkaTemplate since we're sending Map-based events for now - private final KafkaTemplate kafkaTemplate; - - @Autowired - public PolicyActionProducer(KafkaTemplate kafkaTemplate) { - this.kafkaTemplate = kafkaTemplate; - } - - // Method to send policy action events as Map objects for now - public void sendPolicyActionEvent(Map event) { - try { - String assetId = (String) event.get("assetId"); - String actionType = (String) event.get("actionType"); - log.info("Sending PolicyActionEvent to topic '{}': AssetId={}, ActionType={}", - policyActionEventTopic, assetId, actionType); - // Use assetId as key for partitioning - kafkaTemplate.send(policyActionEventTopic, assetId, event); - } catch (Exception e) { - String assetId = event != null ? (String) event.get("assetId") : "unknown"; - log.error("Error sending PolicyActionEvent to Kafka for assetId {}: {}", assetId, e.getMessage(), e); - } - } +package com.dalab.policyengine.kafka.producer; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +@Service +public class PolicyActionProducer { + + private static final Logger log = LoggerFactory.getLogger(PolicyActionProducer.class); + + @Value("${app.kafka.topic.policy-action-event:policy-action-events}") + private String policyActionEventTopic; + + // Use generic KafkaTemplate since we're sending Map-based events for now + private final KafkaTemplate kafkaTemplate; + + @Autowired + public PolicyActionProducer(KafkaTemplate kafkaTemplate) { + this.kafkaTemplate = kafkaTemplate; + } + + // Method to send policy action events as Map objects for now + public void sendPolicyActionEvent(Map event) { + try { + String assetId = (String) event.get("assetId"); + String actionType = (String) event.get("actionType"); + log.info("Sending PolicyActionEvent to topic '{}': AssetId={}, ActionType={}", + policyActionEventTopic, assetId, actionType); + // Use assetId as key for partitioning + kafkaTemplate.send(policyActionEventTopic, assetId, event); + } catch (Exception e) { + String assetId = event != null ? (String) event.get("assetId") : "unknown"; + log.error("Error sending PolicyActionEvent to Kafka for assetId {}: {}", assetId, e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/mapper/PolicyDraftMapper.java b/src/main/java/com/dalab/policyengine/mapper/PolicyDraftMapper.java index 17a9b4b022df826941d0be0568524c303927bb61..48adc51ef3f066b25c85debbdbb02a570e2ecd21 100644 --- a/src/main/java/com/dalab/policyengine/mapper/PolicyDraftMapper.java +++ b/src/main/java/com/dalab/policyengine/mapper/PolicyDraftMapper.java @@ -1,372 +1,372 @@ -package com.dalab.policyengine.mapper; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.springframework.stereotype.Component; - -import com.dalab.policyengine.dto.PolicyDraftInputDTO; -import com.dalab.policyengine.dto.PolicyDraftOutputDTO; -import com.dalab.policyengine.model.PolicyDraft; -import com.dalab.policyengine.model.PolicyDraftStatus; - -/** - * Mapper for converting between PolicyDraft entities and DTOs. - * Handles workflow status calculation and available actions based on current status and user permissions. - */ -@Component -public class PolicyDraftMapper { - - /** - * Convert PolicyDraft entity to output DTO with workflow information. - */ - public PolicyDraftOutputDTO toOutputDTO(PolicyDraft draft) { - if (draft == null) { - return null; - } - - PolicyDraftOutputDTO dto = new PolicyDraftOutputDTO(); - - // Basic information - dto.setId(draft.getId()); - dto.setName(draft.getName()); - dto.setDescription(draft.getDescription()); - dto.setStatus(draft.getStatus().name()); - dto.setVersion(draft.getVersion()); - dto.setBasePolicyId(draft.getBasePolicyId()); - dto.setConditionLogic(draft.getConditionLogic()); - dto.setRulesDefinition(draft.getRulesDefinition()); - dto.setActions(draft.getActions()); - dto.setChangeSummary(draft.getChangeSummary()); - dto.setJustification(draft.getJustification()); - dto.setExpectedImpact(draft.getExpectedImpact()); - dto.setTargetImplementationDate(draft.getTargetImplementationDate()); - dto.setPriority(draft.getPriority()); - dto.setCategory(draft.getCategory()); - dto.setTags(draft.getTags()); - dto.setStakeholders(draft.getStakeholders()); - dto.setApprovalMetadata(draft.getApprovalMetadata()); - dto.setReviewComments(draft.getReviewComments()); - - // Audit trail - dto.setCreatedAt(draft.getCreatedAt()); - dto.setUpdatedAt(draft.getUpdatedAt()); - dto.setCreatedByUserId(draft.getCreatedByUserId()); - dto.setUpdatedByUserId(draft.getUpdatedByUserId()); - dto.setSubmittedAt(draft.getSubmittedAt()); - dto.setSubmittedByUserId(draft.getSubmittedByUserId()); - dto.setApprovedAt(draft.getApprovedAt()); - dto.setApprovedByUserId(draft.getApprovedByUserId()); - dto.setRejectedAt(draft.getRejectedAt()); - dto.setRejectedByUserId(draft.getRejectedByUserId()); - dto.setPublishedAt(draft.getPublishedAt()); - dto.setPublishedByUserId(draft.getPublishedByUserId()); - - // Enhanced workflow information - dto.setWorkflowStatus(createWorkflowStatus(draft)); - dto.setAvailableActions(createAvailableActions(draft)); - - return dto; - } - - /** - * Convert input DTO to PolicyDraft entity. - */ - public PolicyDraft toEntity(PolicyDraftInputDTO inputDTO) { - if (inputDTO == null) { - return null; - } - - PolicyDraft draft = new PolicyDraft(); - - draft.setName(inputDTO.getName()); - draft.setDescription(inputDTO.getDescription()); - draft.setBasePolicyId(inputDTO.getBasePolicyId()); - draft.setConditionLogic(inputDTO.getConditionLogic()); - draft.setRulesDefinition(inputDTO.getRulesDefinition()); - draft.setActions(inputDTO.getActions()); - draft.setChangeSummary(inputDTO.getChangeSummary()); - draft.setJustification(inputDTO.getJustification()); - draft.setExpectedImpact(inputDTO.getExpectedImpact()); - draft.setTargetImplementationDate(inputDTO.getTargetImplementationDate()); - draft.setPriority(inputDTO.getPriority()); - draft.setCategory(inputDTO.getCategory()); - - if (inputDTO.getTags() != null) { - draft.setTags(new ArrayList<>(inputDTO.getTags())); - } - if (inputDTO.getStakeholders() != null) { - draft.setStakeholders(new ArrayList<>(inputDTO.getStakeholders())); - } - - return draft; - } - - /** - * Update entity from input DTO (for updates). - */ - public void updateEntity(PolicyDraft draft, PolicyDraftInputDTO inputDTO) { - if (draft == null || inputDTO == null) { - return; - } - - draft.setName(inputDTO.getName()); - draft.setDescription(inputDTO.getDescription()); - draft.setBasePolicyId(inputDTO.getBasePolicyId()); - draft.setConditionLogic(inputDTO.getConditionLogic()); - draft.setRulesDefinition(inputDTO.getRulesDefinition()); - draft.setActions(inputDTO.getActions()); - draft.setChangeSummary(inputDTO.getChangeSummary()); - draft.setJustification(inputDTO.getJustification()); - draft.setExpectedImpact(inputDTO.getExpectedImpact()); - draft.setTargetImplementationDate(inputDTO.getTargetImplementationDate()); - draft.setPriority(inputDTO.getPriority()); - draft.setCategory(inputDTO.getCategory()); - - if (inputDTO.getTags() != null) { - draft.getTags().clear(); - draft.getTags().addAll(inputDTO.getTags()); - } - if (inputDTO.getStakeholders() != null) { - draft.getStakeholders().clear(); - draft.getStakeholders().addAll(inputDTO.getStakeholders()); - } - } - - /** - * Create workflow status information for the draft. - */ - private PolicyDraftOutputDTO.WorkflowStatusDTO createWorkflowStatus(PolicyDraft draft) { - PolicyDraftOutputDTO.WorkflowStatusDTO status = new PolicyDraftOutputDTO.WorkflowStatusDTO(); - - PolicyDraftStatus currentStatus = draft.getStatus(); - status.setCurrentStage(currentStatus.name()); - status.setStatusDescription(getStatusDescription(currentStatus)); - status.setNextPossibleStates(getNextPossibleStates(currentStatus)); - status.setStageColor(getStageColor(currentStatus)); - - // Calculate days in current status - Instant statusChangeTime = getLastStatusChangeTime(draft, currentStatus); - if (statusChangeTime != null) { - long days = ChronoUnit.DAYS.between(statusChangeTime, Instant.now()); - status.setDaysInCurrentStatus((int) days); - } - - // Set permission flags (simplified - would integrate with security context in real implementation) - status.setCanEdit(canEdit(currentStatus)); - status.setCanSubmit(canSubmit(currentStatus)); - status.setCanApprove(canApprove(currentStatus)); - status.setCanReject(canReject(currentStatus)); - status.setCanPublish(canPublish(currentStatus)); - - return status; - } - - /** - * Create available workflow actions for the draft. - */ - private List createAvailableActions(PolicyDraft draft) { - List actions = new ArrayList<>(); - PolicyDraftStatus status = draft.getStatus(); - - switch (status) { - case CREATED: - case REQUIRES_CHANGES: - actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("SUBMIT", "Submit for Review")); - actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("ARCHIVE", "Archive Draft")); - break; - case SUBMITTED: - case UNDER_REVIEW: - actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("APPROVE", "Approve Draft")); - actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REJECT", "Reject Draft")); - actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REQUEST_CHANGES", "Request Changes")); - break; - case APPROVED: - actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("PUBLISH", "Publish Policy")); - actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REJECT", "Reject Draft")); - break; - default: - // No actions available for REJECTED, PUBLISHED, ARCHIVED - break; - } - - // Set action properties - for (PolicyDraftOutputDTO.WorkflowActionDTO action : actions) { - setActionProperties(action); - } - - return actions; - } - - /** - * Get human-readable description for status. - */ - private String getStatusDescription(PolicyDraftStatus status) { - switch (status) { - case CREATED: - return "Draft has been created and is ready for editing"; - case SUBMITTED: - return "Draft has been submitted and is awaiting review"; - case UNDER_REVIEW: - return "Draft is currently being reviewed"; - case REQUIRES_CHANGES: - return "Draft requires changes based on reviewer feedback"; - case APPROVED: - return "Draft has been approved and is ready for publication"; - case REJECTED: - return "Draft has been rejected and will not be published"; - case PUBLISHED: - return "Draft has been published as an active policy"; - case ARCHIVED: - return "Draft has been archived and is no longer active"; - default: - return "Unknown status"; - } - } - - /** - * Get next possible states for workflow transitions. - */ - private List getNextPossibleStates(PolicyDraftStatus status) { - switch (status) { - case CREATED: - case REQUIRES_CHANGES: - return Arrays.asList("SUBMITTED", "ARCHIVED"); - case SUBMITTED: - return Arrays.asList("UNDER_REVIEW", "APPROVED", "REJECTED", "REQUIRES_CHANGES"); - case UNDER_REVIEW: - return Arrays.asList("APPROVED", "REJECTED", "REQUIRES_CHANGES"); - case APPROVED: - return Arrays.asList("PUBLISHED", "REJECTED"); - default: - return new ArrayList<>(); // Terminal states - } - } - - /** - * Get color for UI status indicators. - */ - private String getStageColor(PolicyDraftStatus status) { - switch (status) { - case CREATED: - return "blue"; - case SUBMITTED: - case UNDER_REVIEW: - return "orange"; - case REQUIRES_CHANGES: - return "yellow"; - case APPROVED: - return "green"; - case REJECTED: - return "red"; - case PUBLISHED: - return "purple"; - case ARCHIVED: - return "gray"; - default: - return "gray"; - } - } - - /** - * Get the timestamp of last status change. - */ - private Instant getLastStatusChangeTime(PolicyDraft draft, PolicyDraftStatus currentStatus) { - switch (currentStatus) { - case SUBMITTED: - return draft.getSubmittedAt(); - case APPROVED: - return draft.getApprovedAt(); - case REJECTED: - return draft.getRejectedAt(); - case PUBLISHED: - return draft.getPublishedAt(); - default: - return draft.getUpdatedAt(); - } - } - - /** - * Check if draft can be edited in current status. - */ - private boolean canEdit(PolicyDraftStatus status) { - return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; - } - - /** - * Check if draft can be submitted in current status. - */ - private boolean canSubmit(PolicyDraftStatus status) { - return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; - } - - /** - * Check if draft can be approved in current status. - */ - private boolean canApprove(PolicyDraftStatus status) { - return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; - } - - /** - * Check if draft can be rejected in current status. - */ - private boolean canReject(PolicyDraftStatus status) { - return status == PolicyDraftStatus.SUBMITTED || - status == PolicyDraftStatus.UNDER_REVIEW || - status == PolicyDraftStatus.APPROVED; - } - - /** - * Check if draft can be published in current status. - */ - private boolean canPublish(PolicyDraftStatus status) { - return status == PolicyDraftStatus.APPROVED; - } - - /** - * Set properties for workflow actions. - */ - private void setActionProperties(PolicyDraftOutputDTO.WorkflowActionDTO action) { - switch (action.getActionType()) { - case "SUBMIT": - action.setActionDescription("Submit the draft for review by approvers"); - action.setRequiresComment(false); - action.setConfirmationMessage("Are you sure you want to submit this draft for review?"); - action.setButtonStyle("primary"); - break; - case "APPROVE": - action.setActionDescription("Approve the draft for publication"); - action.setRequiresComment(false); - action.setConfirmationMessage("Are you sure you want to approve this draft?"); - action.setButtonStyle("success"); - break; - case "REJECT": - action.setActionDescription("Reject the draft and prevent publication"); - action.setRequiresComment(true); - action.setConfirmationMessage("Are you sure you want to reject this draft? This action cannot be undone."); - action.setButtonStyle("danger"); - break; - case "REQUEST_CHANGES": - action.setActionDescription("Request changes from the draft author"); - action.setRequiresComment(true); - action.setConfirmationMessage("Please provide details about the required changes"); - action.setButtonStyle("warning"); - break; - case "PUBLISH": - action.setActionDescription("Publish the draft as an active policy"); - action.setRequiresComment(false); - action.setConfirmationMessage("Are you sure you want to publish this draft as an active policy?"); - action.setButtonStyle("success"); - break; - case "ARCHIVE": - action.setActionDescription("Archive the draft"); - action.setRequiresComment(false); - action.setConfirmationMessage("Are you sure you want to archive this draft?"); - action.setButtonStyle("secondary"); - break; - } - } +package com.dalab.policyengine.mapper; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.dalab.policyengine.dto.PolicyDraftInputDTO; +import com.dalab.policyengine.dto.PolicyDraftOutputDTO; +import com.dalab.policyengine.model.PolicyDraft; +import com.dalab.policyengine.model.PolicyDraftStatus; + +/** + * Mapper for converting between PolicyDraft entities and DTOs. + * Handles workflow status calculation and available actions based on current status and user permissions. + */ +@Component +public class PolicyDraftMapper { + + /** + * Convert PolicyDraft entity to output DTO with workflow information. + */ + public PolicyDraftOutputDTO toOutputDTO(PolicyDraft draft) { + if (draft == null) { + return null; + } + + PolicyDraftOutputDTO dto = new PolicyDraftOutputDTO(); + + // Basic information + dto.setId(draft.getId()); + dto.setName(draft.getName()); + dto.setDescription(draft.getDescription()); + dto.setStatus(draft.getStatus().name()); + dto.setVersion(draft.getVersion()); + dto.setBasePolicyId(draft.getBasePolicyId()); + dto.setConditionLogic(draft.getConditionLogic()); + dto.setRulesDefinition(draft.getRulesDefinition()); + dto.setActions(draft.getActions()); + dto.setChangeSummary(draft.getChangeSummary()); + dto.setJustification(draft.getJustification()); + dto.setExpectedImpact(draft.getExpectedImpact()); + dto.setTargetImplementationDate(draft.getTargetImplementationDate()); + dto.setPriority(draft.getPriority()); + dto.setCategory(draft.getCategory()); + dto.setTags(draft.getTags()); + dto.setStakeholders(draft.getStakeholders()); + dto.setApprovalMetadata(draft.getApprovalMetadata()); + dto.setReviewComments(draft.getReviewComments()); + + // Audit trail + dto.setCreatedAt(draft.getCreatedAt()); + dto.setUpdatedAt(draft.getUpdatedAt()); + dto.setCreatedByUserId(draft.getCreatedByUserId()); + dto.setUpdatedByUserId(draft.getUpdatedByUserId()); + dto.setSubmittedAt(draft.getSubmittedAt()); + dto.setSubmittedByUserId(draft.getSubmittedByUserId()); + dto.setApprovedAt(draft.getApprovedAt()); + dto.setApprovedByUserId(draft.getApprovedByUserId()); + dto.setRejectedAt(draft.getRejectedAt()); + dto.setRejectedByUserId(draft.getRejectedByUserId()); + dto.setPublishedAt(draft.getPublishedAt()); + dto.setPublishedByUserId(draft.getPublishedByUserId()); + + // Enhanced workflow information + dto.setWorkflowStatus(createWorkflowStatus(draft)); + dto.setAvailableActions(createAvailableActions(draft)); + + return dto; + } + + /** + * Convert input DTO to PolicyDraft entity. + */ + public PolicyDraft toEntity(PolicyDraftInputDTO inputDTO) { + if (inputDTO == null) { + return null; + } + + PolicyDraft draft = new PolicyDraft(); + + draft.setName(inputDTO.getName()); + draft.setDescription(inputDTO.getDescription()); + draft.setBasePolicyId(inputDTO.getBasePolicyId()); + draft.setConditionLogic(inputDTO.getConditionLogic()); + draft.setRulesDefinition(inputDTO.getRulesDefinition()); + draft.setActions(inputDTO.getActions()); + draft.setChangeSummary(inputDTO.getChangeSummary()); + draft.setJustification(inputDTO.getJustification()); + draft.setExpectedImpact(inputDTO.getExpectedImpact()); + draft.setTargetImplementationDate(inputDTO.getTargetImplementationDate()); + draft.setPriority(inputDTO.getPriority()); + draft.setCategory(inputDTO.getCategory()); + + if (inputDTO.getTags() != null) { + draft.setTags(new ArrayList<>(inputDTO.getTags())); + } + if (inputDTO.getStakeholders() != null) { + draft.setStakeholders(new ArrayList<>(inputDTO.getStakeholders())); + } + + return draft; + } + + /** + * Update entity from input DTO (for updates). + */ + public void updateEntity(PolicyDraft draft, PolicyDraftInputDTO inputDTO) { + if (draft == null || inputDTO == null) { + return; + } + + draft.setName(inputDTO.getName()); + draft.setDescription(inputDTO.getDescription()); + draft.setBasePolicyId(inputDTO.getBasePolicyId()); + draft.setConditionLogic(inputDTO.getConditionLogic()); + draft.setRulesDefinition(inputDTO.getRulesDefinition()); + draft.setActions(inputDTO.getActions()); + draft.setChangeSummary(inputDTO.getChangeSummary()); + draft.setJustification(inputDTO.getJustification()); + draft.setExpectedImpact(inputDTO.getExpectedImpact()); + draft.setTargetImplementationDate(inputDTO.getTargetImplementationDate()); + draft.setPriority(inputDTO.getPriority()); + draft.setCategory(inputDTO.getCategory()); + + if (inputDTO.getTags() != null) { + draft.getTags().clear(); + draft.getTags().addAll(inputDTO.getTags()); + } + if (inputDTO.getStakeholders() != null) { + draft.getStakeholders().clear(); + draft.getStakeholders().addAll(inputDTO.getStakeholders()); + } + } + + /** + * Create workflow status information for the draft. + */ + private PolicyDraftOutputDTO.WorkflowStatusDTO createWorkflowStatus(PolicyDraft draft) { + PolicyDraftOutputDTO.WorkflowStatusDTO status = new PolicyDraftOutputDTO.WorkflowStatusDTO(); + + PolicyDraftStatus currentStatus = draft.getStatus(); + status.setCurrentStage(currentStatus.name()); + status.setStatusDescription(getStatusDescription(currentStatus)); + status.setNextPossibleStates(getNextPossibleStates(currentStatus)); + status.setStageColor(getStageColor(currentStatus)); + + // Calculate days in current status + Instant statusChangeTime = getLastStatusChangeTime(draft, currentStatus); + if (statusChangeTime != null) { + long days = ChronoUnit.DAYS.between(statusChangeTime, Instant.now()); + status.setDaysInCurrentStatus((int) days); + } + + // Set permission flags (simplified - would integrate with security context in real implementation) + status.setCanEdit(canEdit(currentStatus)); + status.setCanSubmit(canSubmit(currentStatus)); + status.setCanApprove(canApprove(currentStatus)); + status.setCanReject(canReject(currentStatus)); + status.setCanPublish(canPublish(currentStatus)); + + return status; + } + + /** + * Create available workflow actions for the draft. + */ + private List createAvailableActions(PolicyDraft draft) { + List actions = new ArrayList<>(); + PolicyDraftStatus status = draft.getStatus(); + + switch (status) { + case CREATED: + case REQUIRES_CHANGES: + actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("SUBMIT", "Submit for Review")); + actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("ARCHIVE", "Archive Draft")); + break; + case SUBMITTED: + case UNDER_REVIEW: + actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("APPROVE", "Approve Draft")); + actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REJECT", "Reject Draft")); + actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REQUEST_CHANGES", "Request Changes")); + break; + case APPROVED: + actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("PUBLISH", "Publish Policy")); + actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REJECT", "Reject Draft")); + break; + default: + // No actions available for REJECTED, PUBLISHED, ARCHIVED + break; + } + + // Set action properties + for (PolicyDraftOutputDTO.WorkflowActionDTO action : actions) { + setActionProperties(action); + } + + return actions; + } + + /** + * Get human-readable description for status. + */ + private String getStatusDescription(PolicyDraftStatus status) { + switch (status) { + case CREATED: + return "Draft has been created and is ready for editing"; + case SUBMITTED: + return "Draft has been submitted and is awaiting review"; + case UNDER_REVIEW: + return "Draft is currently being reviewed"; + case REQUIRES_CHANGES: + return "Draft requires changes based on reviewer feedback"; + case APPROVED: + return "Draft has been approved and is ready for publication"; + case REJECTED: + return "Draft has been rejected and will not be published"; + case PUBLISHED: + return "Draft has been published as an active policy"; + case ARCHIVED: + return "Draft has been archived and is no longer active"; + default: + return "Unknown status"; + } + } + + /** + * Get next possible states for workflow transitions. + */ + private List getNextPossibleStates(PolicyDraftStatus status) { + switch (status) { + case CREATED: + case REQUIRES_CHANGES: + return Arrays.asList("SUBMITTED", "ARCHIVED"); + case SUBMITTED: + return Arrays.asList("UNDER_REVIEW", "APPROVED", "REJECTED", "REQUIRES_CHANGES"); + case UNDER_REVIEW: + return Arrays.asList("APPROVED", "REJECTED", "REQUIRES_CHANGES"); + case APPROVED: + return Arrays.asList("PUBLISHED", "REJECTED"); + default: + return new ArrayList<>(); // Terminal states + } + } + + /** + * Get color for UI status indicators. + */ + private String getStageColor(PolicyDraftStatus status) { + switch (status) { + case CREATED: + return "blue"; + case SUBMITTED: + case UNDER_REVIEW: + return "orange"; + case REQUIRES_CHANGES: + return "yellow"; + case APPROVED: + return "green"; + case REJECTED: + return "red"; + case PUBLISHED: + return "purple"; + case ARCHIVED: + return "gray"; + default: + return "gray"; + } + } + + /** + * Get the timestamp of last status change. + */ + private Instant getLastStatusChangeTime(PolicyDraft draft, PolicyDraftStatus currentStatus) { + switch (currentStatus) { + case SUBMITTED: + return draft.getSubmittedAt(); + case APPROVED: + return draft.getApprovedAt(); + case REJECTED: + return draft.getRejectedAt(); + case PUBLISHED: + return draft.getPublishedAt(); + default: + return draft.getUpdatedAt(); + } + } + + /** + * Check if draft can be edited in current status. + */ + private boolean canEdit(PolicyDraftStatus status) { + return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; + } + + /** + * Check if draft can be submitted in current status. + */ + private boolean canSubmit(PolicyDraftStatus status) { + return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; + } + + /** + * Check if draft can be approved in current status. + */ + private boolean canApprove(PolicyDraftStatus status) { + return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; + } + + /** + * Check if draft can be rejected in current status. + */ + private boolean canReject(PolicyDraftStatus status) { + return status == PolicyDraftStatus.SUBMITTED || + status == PolicyDraftStatus.UNDER_REVIEW || + status == PolicyDraftStatus.APPROVED; + } + + /** + * Check if draft can be published in current status. + */ + private boolean canPublish(PolicyDraftStatus status) { + return status == PolicyDraftStatus.APPROVED; + } + + /** + * Set properties for workflow actions. + */ + private void setActionProperties(PolicyDraftOutputDTO.WorkflowActionDTO action) { + switch (action.getActionType()) { + case "SUBMIT": + action.setActionDescription("Submit the draft for review by approvers"); + action.setRequiresComment(false); + action.setConfirmationMessage("Are you sure you want to submit this draft for review?"); + action.setButtonStyle("primary"); + break; + case "APPROVE": + action.setActionDescription("Approve the draft for publication"); + action.setRequiresComment(false); + action.setConfirmationMessage("Are you sure you want to approve this draft?"); + action.setButtonStyle("success"); + break; + case "REJECT": + action.setActionDescription("Reject the draft and prevent publication"); + action.setRequiresComment(true); + action.setConfirmationMessage("Are you sure you want to reject this draft? This action cannot be undone."); + action.setButtonStyle("danger"); + break; + case "REQUEST_CHANGES": + action.setActionDescription("Request changes from the draft author"); + action.setRequiresComment(true); + action.setConfirmationMessage("Please provide details about the required changes"); + action.setButtonStyle("warning"); + break; + case "PUBLISH": + action.setActionDescription("Publish the draft as an active policy"); + action.setRequiresComment(false); + action.setConfirmationMessage("Are you sure you want to publish this draft as an active policy?"); + action.setButtonStyle("success"); + break; + case "ARCHIVE": + action.setActionDescription("Archive the draft"); + action.setRequiresComment(false); + action.setConfirmationMessage("Are you sure you want to archive this draft?"); + action.setButtonStyle("secondary"); + break; + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/mapper/PolicyMapper.java b/src/main/java/com/dalab/policyengine/mapper/PolicyMapper.java index 06d5ff8ad2a296913f4b54a11fbbbb8913c26328..7fc5e0c0e0d06e01ded1b26ff53261c356215373 100644 --- a/src/main/java/com/dalab/policyengine/mapper/PolicyMapper.java +++ b/src/main/java/com/dalab/policyengine/mapper/PolicyMapper.java @@ -1,141 +1,141 @@ -package com.dalab.policyengine.mapper; - -import java.util.Collections; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.springframework.stereotype.Component; - -import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; -import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; -// Add missing DTO imports -import com.dalab.policyengine.dto.PolicyInputDTO; -import com.dalab.policyengine.dto.PolicyOutputDTO; -import com.dalab.policyengine.dto.PolicyRuleDTO; -import com.dalab.policyengine.dto.PolicySummaryDTO; -// Add missing entity imports -import com.dalab.policyengine.model.Policy; -import com.dalab.policyengine.model.PolicyEvaluation; -import com.dalab.policyengine.model.PolicyEvaluationStatus; -import com.dalab.policyengine.model.PolicyRule; - -@Component -public class PolicyMapper { - - public PolicySummaryDTO toPolicySummaryDTO(Policy policy) { - if (policy == null) return null; - PolicySummaryDTO dto = new PolicySummaryDTO(); - dto.setId(policy.getId()); - dto.setName(policy.getName()); - dto.setDescription(policy.getDescription()); - dto.setStatus(policy.getStatus()); - dto.setRuleCount(policy.getRules() != null ? policy.getRules().size() : 0); - dto.setCreatedAt(policy.getCreatedAt()); - dto.setUpdatedAt(policy.getUpdatedAt()); - return dto; - } - - public PolicyOutputDTO toPolicyOutputDTO(Policy policy) { - if (policy == null) return null; - PolicyOutputDTO dto = new PolicyOutputDTO(); - dto.setId(policy.getId()); - dto.setName(policy.getName()); - dto.setDescription(policy.getDescription()); - dto.setStatus(policy.getStatus()); - dto.setConditionLogic(policy.getConditionLogic()); - dto.setActions(policy.getActions()); - dto.setRules(policy.getRules().stream().map(this::toPolicyRuleDTO).collect(Collectors.toList())); - dto.setCreatedAt(policy.getCreatedAt()); - dto.setUpdatedAt(policy.getUpdatedAt()); - dto.setCreatedByUserId(policy.getCreatedByUserId()); - dto.setUpdatedByUserId(policy.getUpdatedByUserId()); - return dto; - } - - public PolicyRuleDTO toPolicyRuleDTO(PolicyRule policyRule) { - if (policyRule == null) return null; - PolicyRuleDTO dto = new PolicyRuleDTO(); - dto.setId(policyRule.getId() != null ? policyRule.getId().toString() : null); - dto.setName(policyRule.getName()); - dto.setDescription(policyRule.getDescription()); - dto.setCondition(policyRule.getCondition()); - dto.setPriority(policyRule.getPriority()); - dto.setActions(policyRule.getActions()); - return dto; - } - - public Policy toPolicyEntity(PolicyInputDTO dto) { - if (dto == null) return null; - Policy policy = new Policy(); - updatePolicyEntityFromInputDTO(policy, dto); // Reuse logic for update - return policy; - } - - public void updatePolicyEntityFromInputDTO(Policy policy, PolicyInputDTO dto) { - if (policy == null || dto == null) return; - - policy.setName(dto.getName()); - policy.setDescription(dto.getDescription()); - policy.setStatus(dto.getStatus()); - policy.setConditionLogic(dto.getConditionLogic()); - policy.setActions(dto.getActions()); - - // Handle rules update carefully - if (dto.getRules() != null) { - // Simple strategy: remove all existing and add new ones based on DTO - // More sophisticated merging could be done (e.g. based on rule ID or name) - policy.getRules().clear(); - dto.getRules().forEach(ruleDTO -> policy.addRule(toPolicyRuleEntity(ruleDTO))); - } else { - policy.setRules(Collections.emptyList()); - } - } - - public PolicyRule toPolicyRuleEntity(PolicyRuleDTO dto) { - if (dto == null) return null; - PolicyRule rule = new PolicyRule(); - // ID is not set here for new rules, or could be used to fetch existing if updating - // For simplicity, if ID is present in DTO, we assume it might be used by service layer - // to find and update existing rule, but mapper focuses on new entity creation or field mapping. - if (dto.getId() != null) { - try { - rule.setId(UUID.fromString(dto.getId())); - } catch (IllegalArgumentException e) { - // Handle invalid UUID string if necessary, or let it be null for new entity - } - } - rule.setName(dto.getName()); - rule.setDescription(dto.getDescription()); - rule.setCondition(dto.getCondition()); - rule.setPriority(dto.getPriority()); - rule.setActions(dto.getActions()); - return rule; - } - - public PolicyEvaluationOutputDTO toPolicyEvaluationOutputDTO(PolicyEvaluation evaluation, String policyName) { - if (evaluation == null) return null; - PolicyEvaluationOutputDTO dto = new PolicyEvaluationOutputDTO(); - dto.setId(evaluation.getId()); - dto.setPolicyId(evaluation.getPolicyId()); - dto.setPolicyName(policyName); // Policy name fetched separately for convenience - dto.setTargetAssetId(evaluation.getTargetAssetId()); - dto.setStatus(evaluation.getStatus()); - dto.setEvaluationDetails(evaluation.getEvaluationDetails()); - dto.setTriggeredActions(evaluation.getTriggeredActions()); - dto.setEvaluatedAt(evaluation.getEvaluatedAt()); - dto.setEvaluationTriggeredByUserId(evaluation.getEvaluationTriggeredByUserId()); - return dto; - } - - public PolicyEvaluationSummaryDTO toPolicyEvaluationSummaryDTO(PolicyEvaluation evaluation, String policyName) { - if (evaluation == null) return null; - PolicyEvaluationSummaryDTO dto = new PolicyEvaluationSummaryDTO(); - dto.setId(evaluation.getId()); - dto.setPolicyId(evaluation.getPolicyId()); - dto.setPolicyName(policyName); - dto.setTargetAssetId(evaluation.getTargetAssetId()); - dto.setStatus(evaluation.getStatus()); - dto.setEvaluatedAt(evaluation.getEvaluatedAt()); - return dto; - } +package com.dalab.policyengine.mapper; + +import java.util.Collections; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Component; + +import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; +import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; +// Add missing DTO imports +import com.dalab.policyengine.dto.PolicyInputDTO; +import com.dalab.policyengine.dto.PolicyOutputDTO; +import com.dalab.policyengine.dto.PolicyRuleDTO; +import com.dalab.policyengine.dto.PolicySummaryDTO; +// Add missing entity imports +import com.dalab.policyengine.model.Policy; +import com.dalab.policyengine.model.PolicyEvaluation; +import com.dalab.policyengine.model.PolicyEvaluationStatus; +import com.dalab.policyengine.model.PolicyRule; + +@Component +public class PolicyMapper { + + public PolicySummaryDTO toPolicySummaryDTO(Policy policy) { + if (policy == null) return null; + PolicySummaryDTO dto = new PolicySummaryDTO(); + dto.setId(policy.getId()); + dto.setName(policy.getName()); + dto.setDescription(policy.getDescription()); + dto.setStatus(policy.getStatus()); + dto.setRuleCount(policy.getRules() != null ? policy.getRules().size() : 0); + dto.setCreatedAt(policy.getCreatedAt()); + dto.setUpdatedAt(policy.getUpdatedAt()); + return dto; + } + + public PolicyOutputDTO toPolicyOutputDTO(Policy policy) { + if (policy == null) return null; + PolicyOutputDTO dto = new PolicyOutputDTO(); + dto.setId(policy.getId()); + dto.setName(policy.getName()); + dto.setDescription(policy.getDescription()); + dto.setStatus(policy.getStatus()); + dto.setConditionLogic(policy.getConditionLogic()); + dto.setActions(policy.getActions()); + dto.setRules(policy.getRules().stream().map(this::toPolicyRuleDTO).collect(Collectors.toList())); + dto.setCreatedAt(policy.getCreatedAt()); + dto.setUpdatedAt(policy.getUpdatedAt()); + dto.setCreatedByUserId(policy.getCreatedByUserId()); + dto.setUpdatedByUserId(policy.getUpdatedByUserId()); + return dto; + } + + public PolicyRuleDTO toPolicyRuleDTO(PolicyRule policyRule) { + if (policyRule == null) return null; + PolicyRuleDTO dto = new PolicyRuleDTO(); + dto.setId(policyRule.getId() != null ? policyRule.getId().toString() : null); + dto.setName(policyRule.getName()); + dto.setDescription(policyRule.getDescription()); + dto.setCondition(policyRule.getCondition()); + dto.setPriority(policyRule.getPriority()); + dto.setActions(policyRule.getActions()); + return dto; + } + + public Policy toPolicyEntity(PolicyInputDTO dto) { + if (dto == null) return null; + Policy policy = new Policy(); + updatePolicyEntityFromInputDTO(policy, dto); // Reuse logic for update + return policy; + } + + public void updatePolicyEntityFromInputDTO(Policy policy, PolicyInputDTO dto) { + if (policy == null || dto == null) return; + + policy.setName(dto.getName()); + policy.setDescription(dto.getDescription()); + policy.setStatus(dto.getStatus()); + policy.setConditionLogic(dto.getConditionLogic()); + policy.setActions(dto.getActions()); + + // Handle rules update carefully + if (dto.getRules() != null) { + // Simple strategy: remove all existing and add new ones based on DTO + // More sophisticated merging could be done (e.g. based on rule ID or name) + policy.getRules().clear(); + dto.getRules().forEach(ruleDTO -> policy.addRule(toPolicyRuleEntity(ruleDTO))); + } else { + policy.setRules(Collections.emptyList()); + } + } + + public PolicyRule toPolicyRuleEntity(PolicyRuleDTO dto) { + if (dto == null) return null; + PolicyRule rule = new PolicyRule(); + // ID is not set here for new rules, or could be used to fetch existing if updating + // For simplicity, if ID is present in DTO, we assume it might be used by service layer + // to find and update existing rule, but mapper focuses on new entity creation or field mapping. + if (dto.getId() != null) { + try { + rule.setId(UUID.fromString(dto.getId())); + } catch (IllegalArgumentException e) { + // Handle invalid UUID string if necessary, or let it be null for new entity + } + } + rule.setName(dto.getName()); + rule.setDescription(dto.getDescription()); + rule.setCondition(dto.getCondition()); + rule.setPriority(dto.getPriority()); + rule.setActions(dto.getActions()); + return rule; + } + + public PolicyEvaluationOutputDTO toPolicyEvaluationOutputDTO(PolicyEvaluation evaluation, String policyName) { + if (evaluation == null) return null; + PolicyEvaluationOutputDTO dto = new PolicyEvaluationOutputDTO(); + dto.setId(evaluation.getId()); + dto.setPolicyId(evaluation.getPolicyId()); + dto.setPolicyName(policyName); // Policy name fetched separately for convenience + dto.setTargetAssetId(evaluation.getTargetAssetId()); + dto.setStatus(evaluation.getStatus()); + dto.setEvaluationDetails(evaluation.getEvaluationDetails()); + dto.setTriggeredActions(evaluation.getTriggeredActions()); + dto.setEvaluatedAt(evaluation.getEvaluatedAt()); + dto.setEvaluationTriggeredByUserId(evaluation.getEvaluationTriggeredByUserId()); + return dto; + } + + public PolicyEvaluationSummaryDTO toPolicyEvaluationSummaryDTO(PolicyEvaluation evaluation, String policyName) { + if (evaluation == null) return null; + PolicyEvaluationSummaryDTO dto = new PolicyEvaluationSummaryDTO(); + dto.setId(evaluation.getId()); + dto.setPolicyId(evaluation.getPolicyId()); + dto.setPolicyName(policyName); + dto.setTargetAssetId(evaluation.getTargetAssetId()); + dto.setStatus(evaluation.getStatus()); + dto.setEvaluatedAt(evaluation.getEvaluatedAt()); + return dto; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/EventRule.java b/src/main/java/com/dalab/policyengine/model/EventRule.java index 1f5c102cc11c2ca857566ab683d0a38b9312a5b0..ff774a350153fbbea6170d05c0410201042ba0ac 100644 --- a/src/main/java/com/dalab/policyengine/model/EventRule.java +++ b/src/main/java/com/dalab/policyengine/model/EventRule.java @@ -1,226 +1,226 @@ -package com.dalab.policyengine.model; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * Entity representing a filtering rule for event subscriptions. - * Uses MVEL expressions to define conditions for event matching. - */ -@Entity -@Table(name = "event_rules") -public class EventRule { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(columnDefinition = "UUID") - private UUID id; - - @NotBlank - @Size(max = 255) - @Column(nullable = false) - private String name; - - @Size(max = 500) - private String description; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "subscription_id", nullable = false) - private EventSubscription subscription; - - /** - * MVEL expression for evaluating events - * Example: "eventType == 'POLICY_VIOLATION' && severity == 'HIGH' && assetId.startsWith('prod-')" - */ - @Column(columnDefinition = "TEXT", nullable = false) - private String condition; - - /** - * Rule priority (lower number = higher priority) - */ - @Column(nullable = false) - private Integer priority = 1; - - /** - * Whether this rule is enabled - */ - @Column(nullable = false) - private Boolean enabled = true; - - /** - * Additional parameters for the rule - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map parameters; - - @Column(nullable = false, updatable = false) - private Instant createdAt; - - private Instant updatedAt; - - @Column(columnDefinition = "UUID") - private UUID createdByUserId; - - @Column(columnDefinition = "UUID") - private UUID updatedByUserId; - - // Constructors - public EventRule() {} - - public EventRule(String name, String condition) { - this.name = name; - this.condition = condition; - } - - // Getters and Setters - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public EventSubscription getSubscription() { - return subscription; - } - - public void setSubscription(EventSubscription subscription) { - this.subscription = subscription; - } - - public String getCondition() { - return condition; - } - - public void setCondition(String condition) { - this.condition = condition; - } - - public Integer getPriority() { - return priority; - } - - public void setPriority(Integer priority) { - this.priority = priority; - } - - public Boolean getEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - public Map getParameters() { - return parameters; - } - - public void setParameters(Map parameters) { - this.parameters = parameters; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } - - @PrePersist - protected void onCreate() { - createdAt = Instant.now(); - updatedAt = Instant.now(); - } - - @PreUpdate - protected void onUpdate() { - updatedAt = Instant.now(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof EventRule)) return false; - EventRule eventRule = (EventRule) o; - return id != null && id.equals(eventRule.getId()); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "EventRule{" + - "id=" + id + - ", name='" + name + '\'' + - ", condition='" + condition + '\'' + - ", priority=" + priority + - ", enabled=" + enabled + - '}'; - } +package com.dalab.policyengine.model; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * Entity representing a filtering rule for event subscriptions. + * Uses MVEL expressions to define conditions for event matching. + */ +@Entity +@Table(name = "event_rules") +public class EventRule { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "UUID") + private UUID id; + + @NotBlank + @Size(max = 255) + @Column(nullable = false) + private String name; + + @Size(max = 500) + private String description; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "subscription_id", nullable = false) + private EventSubscription subscription; + + /** + * MVEL expression for evaluating events + * Example: "eventType == 'POLICY_VIOLATION' && severity == 'HIGH' && assetId.startsWith('prod-')" + */ + @Column(columnDefinition = "TEXT", nullable = false) + private String condition; + + /** + * Rule priority (lower number = higher priority) + */ + @Column(nullable = false) + private Integer priority = 1; + + /** + * Whether this rule is enabled + */ + @Column(nullable = false) + private Boolean enabled = true; + + /** + * Additional parameters for the rule + */ + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map parameters; + + @Column(nullable = false, updatable = false) + private Instant createdAt; + + private Instant updatedAt; + + @Column(columnDefinition = "UUID") + private UUID createdByUserId; + + @Column(columnDefinition = "UUID") + private UUID updatedByUserId; + + // Constructors + public EventRule() {} + + public EventRule(String name, String condition) { + this.name = name; + this.condition = condition; + } + + // Getters and Setters + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public EventSubscription getSubscription() { + return subscription; + } + + public void setSubscription(EventSubscription subscription) { + this.subscription = subscription; + } + + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } + + @PrePersist + protected void onCreate() { + createdAt = Instant.now(); + updatedAt = Instant.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = Instant.now(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EventRule)) return false; + EventRule eventRule = (EventRule) o; + return id != null && id.equals(eventRule.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public String toString() { + return "EventRule{" + + "id=" + id + + ", name='" + name + '\'' + + ", condition='" + condition + '\'' + + ", priority=" + priority + + ", enabled=" + enabled + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/EventSeverity.java b/src/main/java/com/dalab/policyengine/model/EventSeverity.java index 2872064e153a652395a1aae3a7450bb34f50c6ce..7401b705b5cc00dbd223f3fbd754e6f87747723f 100644 --- a/src/main/java/com/dalab/policyengine/model/EventSeverity.java +++ b/src/main/java/com/dalab/policyengine/model/EventSeverity.java @@ -1,31 +1,31 @@ -package com.dalab.policyengine.model; - -/** - * Enumeration representing the severity levels of events in the DALab platform. - */ -public enum EventSeverity { - /** - * Critical severity - immediate attention required - */ - CRITICAL, - - /** - * High severity - urgent attention required - */ - HIGH, - - /** - * Medium severity - attention required - */ - MEDIUM, - - /** - * Low severity - informational - */ - LOW, - - /** - * Informational - for logging and tracking purposes - */ - INFO +package com.dalab.policyengine.model; + +/** + * Enumeration representing the severity levels of events in the DALab platform. + */ +public enum EventSeverity { + /** + * Critical severity - immediate attention required + */ + CRITICAL, + + /** + * High severity - urgent attention required + */ + HIGH, + + /** + * Medium severity - attention required + */ + MEDIUM, + + /** + * Low severity - informational + */ + LOW, + + /** + * Informational - for logging and tracking purposes + */ + INFO } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/EventSubscription.java b/src/main/java/com/dalab/policyengine/model/EventSubscription.java index b0b1dbc53ac05fa9306573325f97ffae1ec5c345..75856607ad3ec14f15c91d292f3f2d602a59091a 100644 --- a/src/main/java/com/dalab/policyengine/model/EventSubscription.java +++ b/src/main/java/com/dalab/policyengine/model/EventSubscription.java @@ -1,339 +1,339 @@ -package com.dalab.policyengine.model; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; - -/** - * Entity representing an event subscription configuration. - * Users can subscribe to specific types of events and configure - * notification preferences and action triggers. - */ -@Entity -@Table(name = "event_subscriptions") -public class EventSubscription { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(columnDefinition = "UUID") - private UUID id; - - @NotBlank - @Size(max = 255) - @Column(nullable = false) - private String name; - - @Size(max = 1000) - private String description; - - @NotNull - @Column(columnDefinition = "UUID", nullable = false) - private UUID userId; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private EventSubscriptionStatus status = EventSubscriptionStatus.ACTIVE; - - /** - * Event types to subscribe to (e.g., POLICY_VIOLATION, ASSET_DISCOVERED, COMPLIANCE_ISSUE) - */ - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "event_subscription_types", joinColumns = @JoinColumn(name = "subscription_id")) - @Enumerated(EnumType.STRING) - @Column(name = "event_type") - private List eventTypes = new ArrayList<>(); - - /** - * Event severity levels to include (e.g., HIGH, MEDIUM, LOW) - */ - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "event_subscription_severities", joinColumns = @JoinColumn(name = "subscription_id")) - @Enumerated(EnumType.STRING) - @Column(name = "severity") - private List severities = new ArrayList<>(); - - /** - * Source services to monitor (e.g., da-catalog, da-discovery, da-compliance) - */ - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "event_subscription_sources", joinColumns = @JoinColumn(name = "subscription_id")) - @Column(name = "source_service") - private List sourceServices = new ArrayList<>(); - - /** - * Filter conditions for events (MVEL expressions) - */ - @OneToMany(mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - private List rules = new ArrayList<>(); - - /** - * Notification preferences and action configurations - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map notificationConfig; // e.g., { "email": true, "slack": { "channel": "#alerts" } } - - /** - * Action configurations to trigger when events match - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map actionConfig; // e.g., { "autoQuarantine": true, "escalateTo": "admin" } - - @Column(nullable = false, updatable = false) - private Instant createdAt; - - private Instant updatedAt; - - @Column(columnDefinition = "UUID") - private UUID createdByUserId; - - @Column(columnDefinition = "UUID") - private UUID updatedByUserId; - - // Constructors - public EventSubscription() {} - - public EventSubscription(String name, UUID userId) { - this.name = name; - this.userId = userId; - } - - // Getters and Setters - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public UUID getUserId() { - return userId; - } - - public void setUserId(UUID userId) { - this.userId = userId; - } - - public EventSubscriptionStatus getStatus() { - return status; - } - - public void setStatus(EventSubscriptionStatus status) { - this.status = status; - } - - public List getEventTypes() { - return eventTypes; - } - - public void setEventTypes(List eventTypes) { - this.eventTypes = eventTypes != null ? eventTypes : new ArrayList<>(); - } - - public void addEventType(EventType eventType) { - if (this.eventTypes == null) { - this.eventTypes = new ArrayList<>(); - } - this.eventTypes.add(eventType); - } - - public void removeEventType(EventType eventType) { - if (this.eventTypes != null) { - this.eventTypes.remove(eventType); - } - } - - public List getSeverities() { - return severities; - } - - public void setSeverities(List severities) { - this.severities = severities != null ? severities : new ArrayList<>(); - } - - public void addSeverity(EventSeverity severity) { - if (this.severities == null) { - this.severities = new ArrayList<>(); - } - this.severities.add(severity); - } - - public void removeSeverity(EventSeverity severity) { - if (this.severities != null) { - this.severities.remove(severity); - } - } - - public List getSourceServices() { - return sourceServices; - } - - public void setSourceServices(List sourceServices) { - this.sourceServices = sourceServices != null ? sourceServices : new ArrayList<>(); - } - - public void addSourceService(String sourceService) { - if (this.sourceServices == null) { - this.sourceServices = new ArrayList<>(); - } - this.sourceServices.add(sourceService); - } - - public void removeSourceService(String sourceService) { - if (this.sourceServices != null) { - this.sourceServices.remove(sourceService); - } - } - - public List getRules() { - return rules; - } - - public void setRules(List rules) { - this.rules = rules != null ? rules : new ArrayList<>(); - this.rules.forEach(rule -> rule.setSubscription(this)); - } - - public void addRule(EventRule rule) { - if (this.rules == null) { - this.rules = new ArrayList<>(); - } - this.rules.add(rule); - rule.setSubscription(this); - } - - public void removeRule(EventRule rule) { - if (this.rules != null) { - this.rules.remove(rule); - rule.setSubscription(null); - } - } - - public Map getNotificationConfig() { - return notificationConfig; - } - - public void setNotificationConfig(Map notificationConfig) { - this.notificationConfig = notificationConfig; - } - - public Map getActionConfig() { - return actionConfig; - } - - public void setActionConfig(Map actionConfig) { - this.actionConfig = actionConfig; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } - - @PrePersist - protected void onCreate() { - createdAt = Instant.now(); - updatedAt = Instant.now(); - } - - @PreUpdate - protected void onUpdate() { - updatedAt = Instant.now(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof EventSubscription)) return false; - EventSubscription that = (EventSubscription) o; - return id != null && id.equals(that.getId()); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "EventSubscription{" + - "id=" + id + - ", name='" + name + '\'' + - ", userId=" + userId + - ", status=" + status + - ", eventTypes=" + eventTypes + - ", severities=" + severities + - '}'; - } +package com.dalab.policyengine.model; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * Entity representing an event subscription configuration. + * Users can subscribe to specific types of events and configure + * notification preferences and action triggers. + */ +@Entity +@Table(name = "event_subscriptions") +public class EventSubscription { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "UUID") + private UUID id; + + @NotBlank + @Size(max = 255) + @Column(nullable = false) + private String name; + + @Size(max = 1000) + private String description; + + @NotNull + @Column(columnDefinition = "UUID", nullable = false) + private UUID userId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private EventSubscriptionStatus status = EventSubscriptionStatus.ACTIVE; + + /** + * Event types to subscribe to (e.g., POLICY_VIOLATION, ASSET_DISCOVERED, COMPLIANCE_ISSUE) + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "event_subscription_types", joinColumns = @JoinColumn(name = "subscription_id")) + @Enumerated(EnumType.STRING) + @Column(name = "event_type") + private List eventTypes = new ArrayList<>(); + + /** + * Event severity levels to include (e.g., HIGH, MEDIUM, LOW) + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "event_subscription_severities", joinColumns = @JoinColumn(name = "subscription_id")) + @Enumerated(EnumType.STRING) + @Column(name = "severity") + private List severities = new ArrayList<>(); + + /** + * Source services to monitor (e.g., da-catalog, da-discovery, da-compliance) + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "event_subscription_sources", joinColumns = @JoinColumn(name = "subscription_id")) + @Column(name = "source_service") + private List sourceServices = new ArrayList<>(); + + /** + * Filter conditions for events (MVEL expressions) + */ + @OneToMany(mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private List rules = new ArrayList<>(); + + /** + * Notification preferences and action configurations + */ + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map notificationConfig; // e.g., { "email": true, "slack": { "channel": "#alerts" } } + + /** + * Action configurations to trigger when events match + */ + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map actionConfig; // e.g., { "autoQuarantine": true, "escalateTo": "admin" } + + @Column(nullable = false, updatable = false) + private Instant createdAt; + + private Instant updatedAt; + + @Column(columnDefinition = "UUID") + private UUID createdByUserId; + + @Column(columnDefinition = "UUID") + private UUID updatedByUserId; + + // Constructors + public EventSubscription() {} + + public EventSubscription(String name, UUID userId) { + this.name = name; + this.userId = userId; + } + + // Getters and Setters + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public EventSubscriptionStatus getStatus() { + return status; + } + + public void setStatus(EventSubscriptionStatus status) { + this.status = status; + } + + public List getEventTypes() { + return eventTypes; + } + + public void setEventTypes(List eventTypes) { + this.eventTypes = eventTypes != null ? eventTypes : new ArrayList<>(); + } + + public void addEventType(EventType eventType) { + if (this.eventTypes == null) { + this.eventTypes = new ArrayList<>(); + } + this.eventTypes.add(eventType); + } + + public void removeEventType(EventType eventType) { + if (this.eventTypes != null) { + this.eventTypes.remove(eventType); + } + } + + public List getSeverities() { + return severities; + } + + public void setSeverities(List severities) { + this.severities = severities != null ? severities : new ArrayList<>(); + } + + public void addSeverity(EventSeverity severity) { + if (this.severities == null) { + this.severities = new ArrayList<>(); + } + this.severities.add(severity); + } + + public void removeSeverity(EventSeverity severity) { + if (this.severities != null) { + this.severities.remove(severity); + } + } + + public List getSourceServices() { + return sourceServices; + } + + public void setSourceServices(List sourceServices) { + this.sourceServices = sourceServices != null ? sourceServices : new ArrayList<>(); + } + + public void addSourceService(String sourceService) { + if (this.sourceServices == null) { + this.sourceServices = new ArrayList<>(); + } + this.sourceServices.add(sourceService); + } + + public void removeSourceService(String sourceService) { + if (this.sourceServices != null) { + this.sourceServices.remove(sourceService); + } + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules != null ? rules : new ArrayList<>(); + this.rules.forEach(rule -> rule.setSubscription(this)); + } + + public void addRule(EventRule rule) { + if (this.rules == null) { + this.rules = new ArrayList<>(); + } + this.rules.add(rule); + rule.setSubscription(this); + } + + public void removeRule(EventRule rule) { + if (this.rules != null) { + this.rules.remove(rule); + rule.setSubscription(null); + } + } + + public Map getNotificationConfig() { + return notificationConfig; + } + + public void setNotificationConfig(Map notificationConfig) { + this.notificationConfig = notificationConfig; + } + + public Map getActionConfig() { + return actionConfig; + } + + public void setActionConfig(Map actionConfig) { + this.actionConfig = actionConfig; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } + + @PrePersist + protected void onCreate() { + createdAt = Instant.now(); + updatedAt = Instant.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = Instant.now(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EventSubscription)) return false; + EventSubscription that = (EventSubscription) o; + return id != null && id.equals(that.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public String toString() { + return "EventSubscription{" + + "id=" + id + + ", name='" + name + '\'' + + ", userId=" + userId + + ", status=" + status + + ", eventTypes=" + eventTypes + + ", severities=" + severities + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/EventSubscriptionStatus.java b/src/main/java/com/dalab/policyengine/model/EventSubscriptionStatus.java index f9c9291b63005ee22a07c7b543c29b1a753863b9..27af53a9a00183ed8332df91835bebd3afd38ffd 100644 --- a/src/main/java/com/dalab/policyengine/model/EventSubscriptionStatus.java +++ b/src/main/java/com/dalab/policyengine/model/EventSubscriptionStatus.java @@ -1,26 +1,26 @@ -package com.dalab.policyengine.model; - -/** - * Enumeration representing the status of an event subscription. - */ -public enum EventSubscriptionStatus { - /** - * Subscription is active and receiving events - */ - ACTIVE, - - /** - * Subscription is temporarily paused - */ - PAUSED, - - /** - * Subscription is disabled and not receiving events - */ - DISABLED, - - /** - * Subscription has been archived - */ - ARCHIVED +package com.dalab.policyengine.model; + +/** + * Enumeration representing the status of an event subscription. + */ +public enum EventSubscriptionStatus { + /** + * Subscription is active and receiving events + */ + ACTIVE, + + /** + * Subscription is temporarily paused + */ + PAUSED, + + /** + * Subscription is disabled and not receiving events + */ + DISABLED, + + /** + * Subscription has been archived + */ + ARCHIVED } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/EventType.java b/src/main/java/com/dalab/policyengine/model/EventType.java index cd3f578e6ba486ad8efff0c728352a6f7b36c2ce..9bd7e2c3435d9dbd760b07914eeb00a669b87b83 100644 --- a/src/main/java/com/dalab/policyengine/model/EventType.java +++ b/src/main/java/com/dalab/policyengine/model/EventType.java @@ -1,123 +1,123 @@ -package com.dalab.policyengine.model; - -/** - * Enumeration representing different types of events that can occur in the DALab platform. - */ -public enum EventType { - // Policy-related events - /** - * Policy violation detected - */ - POLICY_VIOLATION, - - /** - * Policy evaluation completed - */ - POLICY_EVALUATION_COMPLETED, - - /** - * Policy applied to assets - */ - POLICY_APPLIED, - - /** - * Policy rule triggered - */ - POLICY_RULE_TRIGGERED, - - // Asset-related events - /** - * New asset discovered - */ - ASSET_DISCOVERED, - - /** - * Asset metadata changed - */ - ASSET_METADATA_CHANGED, - - /** - * Asset classification changed - */ - ASSET_CLASSIFICATION_CHANGED, - - /** - * Asset access pattern detected - */ - ASSET_ACCESS_DETECTED, - - // Compliance-related events - /** - * Compliance violation detected - */ - COMPLIANCE_VIOLATION, - - /** - * Compliance scan completed - */ - COMPLIANCE_SCAN_COMPLETED, - - /** - * Regulatory requirement changed - */ - REGULATORY_CHANGE, - - // Data lifecycle events - /** - * Asset scheduled for archival - */ - ASSET_ARCHIVAL_SCHEDULED, - - /** - * Asset archival completed - */ - ASSET_ARCHIVAL_COMPLETED, - - /** - * Asset deletion scheduled - */ - ASSET_DELETION_SCHEDULED, - - /** - * Asset deletion completed - */ - ASSET_DELETION_COMPLETED, - - // System events - /** - * System alert generated - */ - SYSTEM_ALERT, - - /** - * Service health check failed - */ - SERVICE_HEALTH_ISSUE, - - /** - * Data quality issue detected - */ - DATA_QUALITY_ISSUE, - - /** - * Security incident detected - */ - SECURITY_INCIDENT, - - // User activity events - /** - * User access pattern anomaly - */ - USER_ACCESS_ANOMALY, - - /** - * Unauthorized access attempt - */ - UNAUTHORIZED_ACCESS_ATTEMPT, - - // Generic event type for custom events - /** - * Custom event type - */ - CUSTOM_EVENT +package com.dalab.policyengine.model; + +/** + * Enumeration representing different types of events that can occur in the DALab platform. + */ +public enum EventType { + // Policy-related events + /** + * Policy violation detected + */ + POLICY_VIOLATION, + + /** + * Policy evaluation completed + */ + POLICY_EVALUATION_COMPLETED, + + /** + * Policy applied to assets + */ + POLICY_APPLIED, + + /** + * Policy rule triggered + */ + POLICY_RULE_TRIGGERED, + + // Asset-related events + /** + * New asset discovered + */ + ASSET_DISCOVERED, + + /** + * Asset metadata changed + */ + ASSET_METADATA_CHANGED, + + /** + * Asset classification changed + */ + ASSET_CLASSIFICATION_CHANGED, + + /** + * Asset access pattern detected + */ + ASSET_ACCESS_DETECTED, + + // Compliance-related events + /** + * Compliance violation detected + */ + COMPLIANCE_VIOLATION, + + /** + * Compliance scan completed + */ + COMPLIANCE_SCAN_COMPLETED, + + /** + * Regulatory requirement changed + */ + REGULATORY_CHANGE, + + // Data lifecycle events + /** + * Asset scheduled for archival + */ + ASSET_ARCHIVAL_SCHEDULED, + + /** + * Asset archival completed + */ + ASSET_ARCHIVAL_COMPLETED, + + /** + * Asset deletion scheduled + */ + ASSET_DELETION_SCHEDULED, + + /** + * Asset deletion completed + */ + ASSET_DELETION_COMPLETED, + + // System events + /** + * System alert generated + */ + SYSTEM_ALERT, + + /** + * Service health check failed + */ + SERVICE_HEALTH_ISSUE, + + /** + * Data quality issue detected + */ + DATA_QUALITY_ISSUE, + + /** + * Security incident detected + */ + SECURITY_INCIDENT, + + // User activity events + /** + * User access pattern anomaly + */ + USER_ACCESS_ANOMALY, + + /** + * Unauthorized access attempt + */ + UNAUTHORIZED_ACCESS_ATTEMPT, + + // Generic event type for custom events + /** + * Custom event type + */ + CUSTOM_EVENT } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/Policy.java b/src/main/java/com/dalab/policyengine/model/Policy.java index b626b34ec9b3f7630251c99c3db72a181734dfd5..c8d1707ca6031a96554b2dbe0ad114d60259b934 100644 --- a/src/main/java/com/dalab/policyengine/model/Policy.java +++ b/src/main/java/com/dalab/policyengine/model/Policy.java @@ -1,171 +1,171 @@ -package com.dalab.policyengine.model; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import jakarta.persistence.*; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -@Entity -@Table(name = "policies") -public class Policy { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(columnDefinition = "UUID") - private UUID id; - - @NotBlank - @Size(max = 255) - @Column(nullable = false, unique = true) - private String name; - - @Size(max = 1000) - private String description; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private PolicyStatus status = PolicyStatus.DISABLED; - - // MVEL condition for the policy. If all rules pass, this condition is evaluated. - // Could be null if policy relies solely on its individual rules. - @Column(columnDefinition = "TEXT") - private String conditionLogic; // e.g., "rule1 && (rule2 || rule3)" - - @OneToMany(mappedBy = "policy", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - private List rules = new ArrayList<>(); - - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map actions; // e.g., { "notify": { "email": "admin@example.com" }, "addLabel": "Sensitive" } - - @Column(nullable = false, updatable = false) - private Instant createdAt; - - private Instant updatedAt; - - @Column(columnDefinition = "UUID") - private UUID createdByUserId; - - @Column(columnDefinition = "UUID") - private UUID updatedByUserId; - - // Getters and Setters - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public PolicyStatus getStatus() { - return status; - } - - public void setStatus(PolicyStatus status) { - this.status = status; - } - - public String getConditionLogic() { - return conditionLogic; - } - - public void setConditionLogic(String conditionLogic) { - this.conditionLogic = conditionLogic; - } - - public List getRules() { - return rules; - } - - public void setRules(List rules) { - this.rules = rules; - this.rules.forEach(rule -> rule.setPolicy(this)); - } - - public void addRule(PolicyRule rule) { - this.rules.add(rule); - rule.setPolicy(this); - } - - public void removeRule(PolicyRule rule) { - this.rules.remove(rule); - rule.setPolicy(null); - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } - - @PrePersist - protected void onCreate() { - createdAt = Instant.now(); - updatedAt = Instant.now(); - } - - @PreUpdate - protected void onUpdate() { - updatedAt = Instant.now(); - } +package com.dalab.policyengine.model; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +@Entity +@Table(name = "policies") +public class Policy { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "UUID") + private UUID id; + + @NotBlank + @Size(max = 255) + @Column(nullable = false, unique = true) + private String name; + + @Size(max = 1000) + private String description; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PolicyStatus status = PolicyStatus.DISABLED; + + // MVEL condition for the policy. If all rules pass, this condition is evaluated. + // Could be null if policy relies solely on its individual rules. + @Column(columnDefinition = "TEXT") + private String conditionLogic; // e.g., "rule1 && (rule2 || rule3)" + + @OneToMany(mappedBy = "policy", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private List rules = new ArrayList<>(); + + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map actions; // e.g., { "notify": { "email": "admin@example.com" }, "addLabel": "Sensitive" } + + @Column(nullable = false, updatable = false) + private Instant createdAt; + + private Instant updatedAt; + + @Column(columnDefinition = "UUID") + private UUID createdByUserId; + + @Column(columnDefinition = "UUID") + private UUID updatedByUserId; + + // Getters and Setters + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PolicyStatus getStatus() { + return status; + } + + public void setStatus(PolicyStatus status) { + this.status = status; + } + + public String getConditionLogic() { + return conditionLogic; + } + + public void setConditionLogic(String conditionLogic) { + this.conditionLogic = conditionLogic; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + this.rules.forEach(rule -> rule.setPolicy(this)); + } + + public void addRule(PolicyRule rule) { + this.rules.add(rule); + rule.setPolicy(this); + } + + public void removeRule(PolicyRule rule) { + this.rules.remove(rule); + rule.setPolicy(null); + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } + + @PrePersist + protected void onCreate() { + createdAt = Instant.now(); + updatedAt = Instant.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = Instant.now(); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/PolicyDraft.java b/src/main/java/com/dalab/policyengine/model/PolicyDraft.java index dd3a7ecd6aba0e09bbe135a58f45dedf9d559072..3f09c23a43e241585eff36c2ca13789b07127269 100644 --- a/src/main/java/com/dalab/policyengine/model/PolicyDraft.java +++ b/src/main/java/com/dalab/policyengine/model/PolicyDraft.java @@ -1,495 +1,495 @@ -package com.dalab.policyengine.model; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * Entity representing a policy draft that goes through approval workflow. - * Supports versioning, collaboration, and audit trails. - */ -@Entity -@Table(name = "policy_drafts") -public class PolicyDraft { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(columnDefinition = "UUID") - private UUID id; - - /** - * Name of the policy (must be unique when published). - */ - @NotBlank - @Size(max = 255) - @Column(nullable = false) - private String name; - - /** - * Detailed description of the policy's purpose and scope. - */ - @Size(max = 2000) - @Column(columnDefinition = "TEXT") - private String description; - - /** - * Current status in the approval workflow. - */ - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private PolicyDraftStatus status = PolicyDraftStatus.CREATED; - - /** - * Version number for this draft (incremented on each revision). - */ - @Column(nullable = false) - private Integer version = 1; - - /** - * Reference to the published policy (if this is an update to existing policy). - */ - @Column(columnDefinition = "UUID") - private UUID basePolicyId; - - /** - * MVEL condition logic for the policy evaluation. - */ - @Column(columnDefinition = "TEXT") - private String conditionLogic; - - /** - * JSON representation of policy rules structure. - * Stored as JSON to support complex rule configurations. - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private List> rulesDefinition; - - /** - * JSON representation of actions to be taken when policy is triggered. - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map actions; - - /** - * Change summary describing what was modified in this version. - */ - @Size(max = 1000) - @Column(columnDefinition = "TEXT") - private String changeSummary; - - /** - * Justification for the policy changes or creation. - */ - @Size(max = 2000) - @Column(columnDefinition = "TEXT") - private String justification; - - /** - * Expected impact of implementing this policy. - */ - @Size(max = 1000) - @Column(columnDefinition = "TEXT") - private String expectedImpact; - - /** - * Target implementation date for the policy. - */ - private Instant targetImplementationDate; - - /** - * Priority level for policy implementation (HIGH, MEDIUM, LOW). - */ - @Size(max = 50) - private String priority = "MEDIUM"; - - /** - * Business category or domain this policy applies to. - */ - @Size(max = 100) - private String category; - - /** - * Tags for categorization and searchability. - */ - @ElementCollection - @CollectionTable(name = "policy_draft_tags", joinColumns = @JoinColumn(name = "draft_id")) - @Column(name = "tag") - private List tags = new ArrayList<>(); - - /** - * Stakeholders who should be notified about this policy. - */ - @ElementCollection - @CollectionTable(name = "policy_draft_stakeholders", joinColumns = @JoinColumn(name = "draft_id")) - @Column(name = "stakeholder_id", columnDefinition = "UUID") - private List stakeholders = new ArrayList<>(); - - /** - * Approval workflow metadata. - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map approvalMetadata; - - /** - * Reviewer comments and feedback. - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private List> reviewComments = new ArrayList<>(); - - // Audit fields - @Column(nullable = false, updatable = false) - private Instant createdAt; - - private Instant updatedAt; - - @Column(columnDefinition = "UUID", nullable = false) - private UUID createdByUserId; - - @Column(columnDefinition = "UUID") - private UUID updatedByUserId; - - private Instant submittedAt; - - @Column(columnDefinition = "UUID") - private UUID submittedByUserId; - - private Instant approvedAt; - - @Column(columnDefinition = "UUID") - private UUID approvedByUserId; - - private Instant rejectedAt; - - @Column(columnDefinition = "UUID") - private UUID rejectedByUserId; - - private Instant publishedAt; - - @Column(columnDefinition = "UUID") - private UUID publishedByUserId; - - // Constructors - public PolicyDraft() {} - - public PolicyDraft(String name, String description, UUID createdByUserId) { - this.name = name; - this.description = description; - this.createdByUserId = createdByUserId; - } - - // Getters and Setters - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public PolicyDraftStatus getStatus() { - return status; - } - - public void setStatus(PolicyDraftStatus status) { - this.status = status; - } - - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } - - public UUID getBasePolicyId() { - return basePolicyId; - } - - public void setBasePolicyId(UUID basePolicyId) { - this.basePolicyId = basePolicyId; - } - - public String getConditionLogic() { - return conditionLogic; - } - - public void setConditionLogic(String conditionLogic) { - this.conditionLogic = conditionLogic; - } - - public List> getRulesDefinition() { - return rulesDefinition; - } - - public void setRulesDefinition(List> rulesDefinition) { - this.rulesDefinition = rulesDefinition; - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } - - public String getChangeSummary() { - return changeSummary; - } - - public void setChangeSummary(String changeSummary) { - this.changeSummary = changeSummary; - } - - public String getJustification() { - return justification; - } - - public void setJustification(String justification) { - this.justification = justification; - } - - public String getExpectedImpact() { - return expectedImpact; - } - - public void setExpectedImpact(String expectedImpact) { - this.expectedImpact = expectedImpact; - } - - public Instant getTargetImplementationDate() { - return targetImplementationDate; - } - - public void setTargetImplementationDate(Instant targetImplementationDate) { - this.targetImplementationDate = targetImplementationDate; - } - - public String getPriority() { - return priority; - } - - public void setPriority(String priority) { - this.priority = priority; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - - public List getStakeholders() { - return stakeholders; - } - - public void setStakeholders(List stakeholders) { - this.stakeholders = stakeholders; - } - - public Map getApprovalMetadata() { - return approvalMetadata; - } - - public void setApprovalMetadata(Map approvalMetadata) { - this.approvalMetadata = approvalMetadata; - } - - public List> getReviewComments() { - return reviewComments; - } - - public void setReviewComments(List> reviewComments) { - this.reviewComments = reviewComments; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - public UUID getCreatedByUserId() { - return createdByUserId; - } - - public void setCreatedByUserId(UUID createdByUserId) { - this.createdByUserId = createdByUserId; - } - - public UUID getUpdatedByUserId() { - return updatedByUserId; - } - - public void setUpdatedByUserId(UUID updatedByUserId) { - this.updatedByUserId = updatedByUserId; - } - - public Instant getSubmittedAt() { - return submittedAt; - } - - public void setSubmittedAt(Instant submittedAt) { - this.submittedAt = submittedAt; - } - - public UUID getSubmittedByUserId() { - return submittedByUserId; - } - - public void setSubmittedByUserId(UUID submittedByUserId) { - this.submittedByUserId = submittedByUserId; - } - - public Instant getApprovedAt() { - return approvedAt; - } - - public void setApprovedAt(Instant approvedAt) { - this.approvedAt = approvedAt; - } - - public UUID getApprovedByUserId() { - return approvedByUserId; - } - - public void setApprovedByUserId(UUID approvedByUserId) { - this.approvedByUserId = approvedByUserId; - } - - public Instant getRejectedAt() { - return rejectedAt; - } - - public void setRejectedAt(Instant rejectedAt) { - this.rejectedAt = rejectedAt; - } - - public UUID getRejectedByUserId() { - return rejectedByUserId; - } - - public void setRejectedByUserId(UUID rejectedByUserId) { - this.rejectedByUserId = rejectedByUserId; - } - - public Instant getPublishedAt() { - return publishedAt; - } - - public void setPublishedAt(Instant publishedAt) { - this.publishedAt = publishedAt; - } - - public UUID getPublishedByUserId() { - return publishedByUserId; - } - - public void setPublishedByUserId(UUID publishedByUserId) { - this.publishedByUserId = publishedByUserId; - } - - /** - * Utility method to add a review comment. - */ - public void addReviewComment(String comment, UUID reviewerId, String reviewerRole) { - Map commentData = Map.of( - "comment", comment, - "reviewerId", reviewerId.toString(), - "reviewerRole", reviewerRole, - "timestamp", Instant.now().toString() - ); - this.reviewComments.add(commentData); - } - - /** - * Utility method to add a tag if not already present. - */ - public void addTag(String tag) { - if (!this.tags.contains(tag)) { - this.tags.add(tag); - } - } - - /** - * Utility method to add a stakeholder if not already present. - */ - public void addStakeholder(UUID stakeholderId) { - if (!this.stakeholders.contains(stakeholderId)) { - this.stakeholders.add(stakeholderId); - } - } - - @PrePersist - protected void onCreate() { - createdAt = Instant.now(); - updatedAt = Instant.now(); - } - - @PreUpdate - protected void onUpdate() { - updatedAt = Instant.now(); - } +package com.dalab.policyengine.model; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * Entity representing a policy draft that goes through approval workflow. + * Supports versioning, collaboration, and audit trails. + */ +@Entity +@Table(name = "policy_drafts") +public class PolicyDraft { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "UUID") + private UUID id; + + /** + * Name of the policy (must be unique when published). + */ + @NotBlank + @Size(max = 255) + @Column(nullable = false) + private String name; + + /** + * Detailed description of the policy's purpose and scope. + */ + @Size(max = 2000) + @Column(columnDefinition = "TEXT") + private String description; + + /** + * Current status in the approval workflow. + */ + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PolicyDraftStatus status = PolicyDraftStatus.CREATED; + + /** + * Version number for this draft (incremented on each revision). + */ + @Column(nullable = false) + private Integer version = 1; + + /** + * Reference to the published policy (if this is an update to existing policy). + */ + @Column(columnDefinition = "UUID") + private UUID basePolicyId; + + /** + * MVEL condition logic for the policy evaluation. + */ + @Column(columnDefinition = "TEXT") + private String conditionLogic; + + /** + * JSON representation of policy rules structure. + * Stored as JSON to support complex rule configurations. + */ + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private List> rulesDefinition; + + /** + * JSON representation of actions to be taken when policy is triggered. + */ + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map actions; + + /** + * Change summary describing what was modified in this version. + */ + @Size(max = 1000) + @Column(columnDefinition = "TEXT") + private String changeSummary; + + /** + * Justification for the policy changes or creation. + */ + @Size(max = 2000) + @Column(columnDefinition = "TEXT") + private String justification; + + /** + * Expected impact of implementing this policy. + */ + @Size(max = 1000) + @Column(columnDefinition = "TEXT") + private String expectedImpact; + + /** + * Target implementation date for the policy. + */ + private Instant targetImplementationDate; + + /** + * Priority level for policy implementation (HIGH, MEDIUM, LOW). + */ + @Size(max = 50) + private String priority = "MEDIUM"; + + /** + * Business category or domain this policy applies to. + */ + @Size(max = 100) + private String category; + + /** + * Tags for categorization and searchability. + */ + @ElementCollection + @CollectionTable(name = "policy_draft_tags", joinColumns = @JoinColumn(name = "draft_id")) + @Column(name = "tag") + private List tags = new ArrayList<>(); + + /** + * Stakeholders who should be notified about this policy. + */ + @ElementCollection + @CollectionTable(name = "policy_draft_stakeholders", joinColumns = @JoinColumn(name = "draft_id")) + @Column(name = "stakeholder_id", columnDefinition = "UUID") + private List stakeholders = new ArrayList<>(); + + /** + * Approval workflow metadata. + */ + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map approvalMetadata; + + /** + * Reviewer comments and feedback. + */ + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private List> reviewComments = new ArrayList<>(); + + // Audit fields + @Column(nullable = false, updatable = false) + private Instant createdAt; + + private Instant updatedAt; + + @Column(columnDefinition = "UUID", nullable = false) + private UUID createdByUserId; + + @Column(columnDefinition = "UUID") + private UUID updatedByUserId; + + private Instant submittedAt; + + @Column(columnDefinition = "UUID") + private UUID submittedByUserId; + + private Instant approvedAt; + + @Column(columnDefinition = "UUID") + private UUID approvedByUserId; + + private Instant rejectedAt; + + @Column(columnDefinition = "UUID") + private UUID rejectedByUserId; + + private Instant publishedAt; + + @Column(columnDefinition = "UUID") + private UUID publishedByUserId; + + // Constructors + public PolicyDraft() {} + + public PolicyDraft(String name, String description, UUID createdByUserId) { + this.name = name; + this.description = description; + this.createdByUserId = createdByUserId; + } + + // Getters and Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PolicyDraftStatus getStatus() { + return status; + } + + public void setStatus(PolicyDraftStatus status) { + this.status = status; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public UUID getBasePolicyId() { + return basePolicyId; + } + + public void setBasePolicyId(UUID basePolicyId) { + this.basePolicyId = basePolicyId; + } + + public String getConditionLogic() { + return conditionLogic; + } + + public void setConditionLogic(String conditionLogic) { + this.conditionLogic = conditionLogic; + } + + public List> getRulesDefinition() { + return rulesDefinition; + } + + public void setRulesDefinition(List> rulesDefinition) { + this.rulesDefinition = rulesDefinition; + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } + + public String getChangeSummary() { + return changeSummary; + } + + public void setChangeSummary(String changeSummary) { + this.changeSummary = changeSummary; + } + + public String getJustification() { + return justification; + } + + public void setJustification(String justification) { + this.justification = justification; + } + + public String getExpectedImpact() { + return expectedImpact; + } + + public void setExpectedImpact(String expectedImpact) { + this.expectedImpact = expectedImpact; + } + + public Instant getTargetImplementationDate() { + return targetImplementationDate; + } + + public void setTargetImplementationDate(Instant targetImplementationDate) { + this.targetImplementationDate = targetImplementationDate; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public List getStakeholders() { + return stakeholders; + } + + public void setStakeholders(List stakeholders) { + this.stakeholders = stakeholders; + } + + public Map getApprovalMetadata() { + return approvalMetadata; + } + + public void setApprovalMetadata(Map approvalMetadata) { + this.approvalMetadata = approvalMetadata; + } + + public List> getReviewComments() { + return reviewComments; + } + + public void setReviewComments(List> reviewComments) { + this.reviewComments = reviewComments; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public UUID getCreatedByUserId() { + return createdByUserId; + } + + public void setCreatedByUserId(UUID createdByUserId) { + this.createdByUserId = createdByUserId; + } + + public UUID getUpdatedByUserId() { + return updatedByUserId; + } + + public void setUpdatedByUserId(UUID updatedByUserId) { + this.updatedByUserId = updatedByUserId; + } + + public Instant getSubmittedAt() { + return submittedAt; + } + + public void setSubmittedAt(Instant submittedAt) { + this.submittedAt = submittedAt; + } + + public UUID getSubmittedByUserId() { + return submittedByUserId; + } + + public void setSubmittedByUserId(UUID submittedByUserId) { + this.submittedByUserId = submittedByUserId; + } + + public Instant getApprovedAt() { + return approvedAt; + } + + public void setApprovedAt(Instant approvedAt) { + this.approvedAt = approvedAt; + } + + public UUID getApprovedByUserId() { + return approvedByUserId; + } + + public void setApprovedByUserId(UUID approvedByUserId) { + this.approvedByUserId = approvedByUserId; + } + + public Instant getRejectedAt() { + return rejectedAt; + } + + public void setRejectedAt(Instant rejectedAt) { + this.rejectedAt = rejectedAt; + } + + public UUID getRejectedByUserId() { + return rejectedByUserId; + } + + public void setRejectedByUserId(UUID rejectedByUserId) { + this.rejectedByUserId = rejectedByUserId; + } + + public Instant getPublishedAt() { + return publishedAt; + } + + public void setPublishedAt(Instant publishedAt) { + this.publishedAt = publishedAt; + } + + public UUID getPublishedByUserId() { + return publishedByUserId; + } + + public void setPublishedByUserId(UUID publishedByUserId) { + this.publishedByUserId = publishedByUserId; + } + + /** + * Utility method to add a review comment. + */ + public void addReviewComment(String comment, UUID reviewerId, String reviewerRole) { + Map commentData = Map.of( + "comment", comment, + "reviewerId", reviewerId.toString(), + "reviewerRole", reviewerRole, + "timestamp", Instant.now().toString() + ); + this.reviewComments.add(commentData); + } + + /** + * Utility method to add a tag if not already present. + */ + public void addTag(String tag) { + if (!this.tags.contains(tag)) { + this.tags.add(tag); + } + } + + /** + * Utility method to add a stakeholder if not already present. + */ + public void addStakeholder(UUID stakeholderId) { + if (!this.stakeholders.contains(stakeholderId)) { + this.stakeholders.add(stakeholderId); + } + } + + @PrePersist + protected void onCreate() { + createdAt = Instant.now(); + updatedAt = Instant.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = Instant.now(); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/PolicyDraftStatus.java b/src/main/java/com/dalab/policyengine/model/PolicyDraftStatus.java index ff1e21725ac50d5311fd4e9db62b720a0129235a..fa2e235059537f98bdf87dd3a672401e96f719c6 100644 --- a/src/main/java/com/dalab/policyengine/model/PolicyDraftStatus.java +++ b/src/main/java/com/dalab/policyengine/model/PolicyDraftStatus.java @@ -1,47 +1,47 @@ -package com.dalab.policyengine.model; - -/** - * Status enumeration for policy drafts supporting complete workflow lifecycle. - * Tracks the draft from creation through approval and publication. - */ -public enum PolicyDraftStatus { - /** - * Draft has been created but not yet submitted for review. - */ - CREATED, - - /** - * Draft has been submitted and is awaiting review. - */ - SUBMITTED, - - /** - * Draft is currently under review by approvers. - */ - UNDER_REVIEW, - - /** - * Draft requires changes based on reviewer feedback. - */ - REQUIRES_CHANGES, - - /** - * Draft has been approved and is ready for publication. - */ - APPROVED, - - /** - * Draft has been rejected and will not be published. - */ - REJECTED, - - /** - * Draft has been published as an active policy. - */ - PUBLISHED, - - /** - * Draft has been archived (no longer active). - */ - ARCHIVED +package com.dalab.policyengine.model; + +/** + * Status enumeration for policy drafts supporting complete workflow lifecycle. + * Tracks the draft from creation through approval and publication. + */ +public enum PolicyDraftStatus { + /** + * Draft has been created but not yet submitted for review. + */ + CREATED, + + /** + * Draft has been submitted and is awaiting review. + */ + SUBMITTED, + + /** + * Draft is currently under review by approvers. + */ + UNDER_REVIEW, + + /** + * Draft requires changes based on reviewer feedback. + */ + REQUIRES_CHANGES, + + /** + * Draft has been approved and is ready for publication. + */ + APPROVED, + + /** + * Draft has been rejected and will not be published. + */ + REJECTED, + + /** + * Draft has been published as an active policy. + */ + PUBLISHED, + + /** + * Draft has been archived (no longer active). + */ + ARCHIVED } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/PolicyEvaluation.java b/src/main/java/com/dalab/policyengine/model/PolicyEvaluation.java index baa5e10b237047ba1b47f00cc7d672c9c494675a..5377a4c85a67600eaa803a4c75fd5b12e68238d6 100644 --- a/src/main/java/com/dalab/policyengine/model/PolicyEvaluation.java +++ b/src/main/java/com/dalab/policyengine/model/PolicyEvaluation.java @@ -1,119 +1,119 @@ -package com.dalab.policyengine.model; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import jakarta.persistence.*; - -@Entity -@Table(name = "policy_evaluations") -public class PolicyEvaluation { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(columnDefinition = "UUID") - private UUID id; - - @Column(nullable = false, columnDefinition = "UUID") - private UUID policyId; - - @Column(nullable = false, length = 255) // External asset ID from da-catalog or other source - private String targetAssetId; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private PolicyEvaluationStatus status; - - // Store details about the evaluation, e.g., which rules passed/failed, input facts snapshot - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map evaluationDetails; - - // Store actions that were triggered as a result of this evaluation - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map triggeredActions; - - @Column(nullable = false) - private Instant evaluatedAt; - - @Column(columnDefinition = "UUID") - private UUID evaluationTriggeredByUserId; // Optional: if triggered by a user action - - // Getters and Setters - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getPolicyId() { - return policyId; - } - - public void setPolicyId(UUID policyId) { - this.policyId = policyId; - } - - public String getTargetAssetId() { - return targetAssetId; - } - - public void setTargetAssetId(String targetAssetId) { - this.targetAssetId = targetAssetId; - } - - public PolicyEvaluationStatus getStatus() { - return status; - } - - public void setStatus(PolicyEvaluationStatus status) { - this.status = status; - } - - public Map getEvaluationDetails() { - return evaluationDetails; - } - - public void setEvaluationDetails(Map evaluationDetails) { - this.evaluationDetails = evaluationDetails; - } - - public Map getTriggeredActions() { - return triggeredActions; - } - - public void setTriggeredActions(Map triggeredActions) { - this.triggeredActions = triggeredActions; - } - - public Instant getEvaluatedAt() { - return evaluatedAt; - } - - public void setEvaluatedAt(Instant evaluatedAt) { - this.evaluatedAt = evaluatedAt; - } - - public UUID getEvaluationTriggeredByUserId() { - return evaluationTriggeredByUserId; - } - - public void setEvaluationTriggeredByUserId(UUID evaluationTriggeredByUserId) { - this.evaluationTriggeredByUserId = evaluationTriggeredByUserId; - } - - @PrePersist - protected void onPersist() { - if (evaluatedAt == null) { - evaluatedAt = Instant.now(); - } - } +package com.dalab.policyengine.model; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import jakarta.persistence.*; + +@Entity +@Table(name = "policy_evaluations") +public class PolicyEvaluation { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "UUID") + private UUID id; + + @Column(nullable = false, columnDefinition = "UUID") + private UUID policyId; + + @Column(nullable = false, length = 255) // External asset ID from da-catalog or other source + private String targetAssetId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PolicyEvaluationStatus status; + + // Store details about the evaluation, e.g., which rules passed/failed, input facts snapshot + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map evaluationDetails; + + // Store actions that were triggered as a result of this evaluation + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map triggeredActions; + + @Column(nullable = false) + private Instant evaluatedAt; + + @Column(columnDefinition = "UUID") + private UUID evaluationTriggeredByUserId; // Optional: if triggered by a user action + + // Getters and Setters + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getPolicyId() { + return policyId; + } + + public void setPolicyId(UUID policyId) { + this.policyId = policyId; + } + + public String getTargetAssetId() { + return targetAssetId; + } + + public void setTargetAssetId(String targetAssetId) { + this.targetAssetId = targetAssetId; + } + + public PolicyEvaluationStatus getStatus() { + return status; + } + + public void setStatus(PolicyEvaluationStatus status) { + this.status = status; + } + + public Map getEvaluationDetails() { + return evaluationDetails; + } + + public void setEvaluationDetails(Map evaluationDetails) { + this.evaluationDetails = evaluationDetails; + } + + public Map getTriggeredActions() { + return triggeredActions; + } + + public void setTriggeredActions(Map triggeredActions) { + this.triggeredActions = triggeredActions; + } + + public Instant getEvaluatedAt() { + return evaluatedAt; + } + + public void setEvaluatedAt(Instant evaluatedAt) { + this.evaluatedAt = evaluatedAt; + } + + public UUID getEvaluationTriggeredByUserId() { + return evaluationTriggeredByUserId; + } + + public void setEvaluationTriggeredByUserId(UUID evaluationTriggeredByUserId) { + this.evaluationTriggeredByUserId = evaluationTriggeredByUserId; + } + + @PrePersist + protected void onPersist() { + if (evaluatedAt == null) { + evaluatedAt = Instant.now(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/PolicyEvaluationStatus.java b/src/main/java/com/dalab/policyengine/model/PolicyEvaluationStatus.java index 57ad0dd2f327dc23ada8cb6239e5985b4126c168..3619761b21fb7e4a8016e7f77391cb555c122a87 100644 --- a/src/main/java/com/dalab/policyengine/model/PolicyEvaluationStatus.java +++ b/src/main/java/com/dalab/policyengine/model/PolicyEvaluationStatus.java @@ -1,9 +1,9 @@ -package com.dalab.policyengine.model; - -public enum PolicyEvaluationStatus { - PASS, // Policy conditions met, actions may have been triggered - FAIL, // Policy conditions not met - ERROR, // Error during evaluation (e.g., bad rule syntax, fact unavailable) - PENDING, // Evaluation is queued or in progress - NOT_APPLICABLE // Policy was not applicable to the target asset +package com.dalab.policyengine.model; + +public enum PolicyEvaluationStatus { + PASS, // Policy conditions met, actions may have been triggered + FAIL, // Policy conditions not met + ERROR, // Error during evaluation (e.g., bad rule syntax, fact unavailable) + PENDING, // Evaluation is queued or in progress + NOT_APPLICABLE // Policy was not applicable to the target asset } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/PolicyRule.java b/src/main/java/com/dalab/policyengine/model/PolicyRule.java index 9c3e9a21fce9f90297b7123d6703cb22b9b9d83b..a540a3fae26ed14352ecae06bedc1c1a7010bafb 100644 --- a/src/main/java/com/dalab/policyengine/model/PolicyRule.java +++ b/src/main/java/com/dalab/policyengine/model/PolicyRule.java @@ -1,137 +1,137 @@ -package com.dalab.policyengine.model; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import jakarta.persistence.*; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -@Entity -@Table(name = "policy_rules") -public class PolicyRule { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(columnDefinition = "UUID") - private UUID id; - - @NotBlank - @Size(max = 255) - @Column(nullable = false) - private String name; // A unique name for the rule within the policy, e.g., "rule1", "checkPII" - - @Size(max = 1000) - private String description; - - @NotBlank - @Column(nullable = false, columnDefinition = "TEXT") - private String condition; // MVEL expression, e.g., "asset.assetType == 'S3_BUCKET' && asset.tags.contains('PII')" - - // Rules with lower numbers have higher priority - @Column(nullable = false) - private int priority = 1; - - // Optional: Actions specific to this rule, if different from policy-level actions or to augment them - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "jsonb") - private Map actions; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "policy_id", nullable = false) - private Policy policy; - - @Column(nullable = false, updatable = false) - private Instant createdAt; - - private Instant updatedAt; - - // Getters and Setters - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getCondition() { - return condition; - } - - public void setCondition(String condition) { - this.condition = condition; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - public Map getActions() { - return actions; - } - - public void setActions(Map actions) { - this.actions = actions; - } - - public Policy getPolicy() { - return policy; - } - - public void setPolicy(Policy policy) { - this.policy = policy; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - - @PrePersist - protected void onCreate() { - createdAt = Instant.now(); - updatedAt = Instant.now(); - } - - @PreUpdate - protected void onUpdate() { - updatedAt = Instant.now(); - } +package com.dalab.policyengine.model; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +@Entity +@Table(name = "policy_rules") +public class PolicyRule { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "UUID") + private UUID id; + + @NotBlank + @Size(max = 255) + @Column(nullable = false) + private String name; // A unique name for the rule within the policy, e.g., "rule1", "checkPII" + + @Size(max = 1000) + private String description; + + @NotBlank + @Column(nullable = false, columnDefinition = "TEXT") + private String condition; // MVEL expression, e.g., "asset.assetType == 'S3_BUCKET' && asset.tags.contains('PII')" + + // Rules with lower numbers have higher priority + @Column(nullable = false) + private int priority = 1; + + // Optional: Actions specific to this rule, if different from policy-level actions or to augment them + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private Map actions; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "policy_id", nullable = false) + private Policy policy; + + @Column(nullable = false, updatable = false) + private Instant createdAt; + + private Instant updatedAt; + + // Getters and Setters + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public Map getActions() { + return actions; + } + + public void setActions(Map actions) { + this.actions = actions; + } + + public Policy getPolicy() { + return policy; + } + + public void setPolicy(Policy policy) { + this.policy = policy; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + @PrePersist + protected void onCreate() { + createdAt = Instant.now(); + updatedAt = Instant.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = Instant.now(); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/model/PolicyStatus.java b/src/main/java/com/dalab/policyengine/model/PolicyStatus.java index 8beec86bcc07968276a4ef0588d61b05c8c6174e..35866ca87c8d41a014a299be12d4698ed0cf1793 100644 --- a/src/main/java/com/dalab/policyengine/model/PolicyStatus.java +++ b/src/main/java/com/dalab/policyengine/model/PolicyStatus.java @@ -1,6 +1,6 @@ -package com.dalab.policyengine.model; - -public enum PolicyStatus { - ENABLED, - DISABLED +package com.dalab.policyengine.model; + +public enum PolicyStatus { + ENABLED, + DISABLED } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/repository/EventRuleRepository.java b/src/main/java/com/dalab/policyengine/repository/EventRuleRepository.java index 44989d62a357f9ea5203d7b1ec622d5519560140..3526496a04b36f52e1142111bc025596588eec05 100644 --- a/src/main/java/com/dalab/policyengine/repository/EventRuleRepository.java +++ b/src/main/java/com/dalab/policyengine/repository/EventRuleRepository.java @@ -1,86 +1,86 @@ -package com.dalab.policyengine.repository; - -import java.util.List; -import java.util.UUID; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import com.dalab.policyengine.model.EventRule; - -/** - * Repository interface for EventRule entities. - * Provides data access methods for event rule management. - */ -@Repository -public interface EventRuleRepository extends JpaRepository { - - /** - * Find all rules for a specific subscription - */ - List findBySubscriptionId(UUID subscriptionId); - - /** - * Find enabled rules for a specific subscription ordered by priority - */ - List findBySubscriptionIdAndEnabledTrueOrderByPriorityAsc(UUID subscriptionId); - - /** - * Find rules by name pattern - */ - List findByNameContainingIgnoreCase(String namePattern); - - /** - * Find rules for a subscription by name pattern - */ - List findBySubscriptionIdAndNameContainingIgnoreCase(UUID subscriptionId, String namePattern); - - /** - * Check if a rule with the given name already exists for a subscription - */ - boolean existsBySubscriptionIdAndName(UUID subscriptionId, String name); - - /** - * Find rules by enabled status - */ - List findByEnabled(Boolean enabled); - - /** - * Find rules with a specific priority - */ - List findByPriority(Integer priority); - - /** - * Find rules created by a specific user - */ - List findByCreatedByUserId(UUID createdByUserId); - - /** - * Count rules for a specific subscription - */ - long countBySubscriptionId(UUID subscriptionId); - - /** - * Count enabled rules for a specific subscription - */ - long countBySubscriptionIdAndEnabledTrue(UUID subscriptionId); - - /** - * Find rules that contain a specific condition pattern - */ - @Query("SELECT er FROM EventRule er WHERE er.condition LIKE %:conditionPattern%") - List findByConditionContaining(@Param("conditionPattern") String conditionPattern); - - /** - * Find the highest priority rule for a subscription - */ - @Query("SELECT er FROM EventRule er WHERE er.subscription.id = :subscriptionId AND er.enabled = true ORDER BY er.priority ASC LIMIT 1") - EventRule findHighestPriorityEnabledRule(@Param("subscriptionId") UUID subscriptionId); - - /** - * Find rules for a subscription ordered by priority - */ - List findBySubscriptionIdOrderByPriorityAsc(UUID subscriptionId); +package com.dalab.policyengine.repository; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.dalab.policyengine.model.EventRule; + +/** + * Repository interface for EventRule entities. + * Provides data access methods for event rule management. + */ +@Repository +public interface EventRuleRepository extends JpaRepository { + + /** + * Find all rules for a specific subscription + */ + List findBySubscriptionId(UUID subscriptionId); + + /** + * Find enabled rules for a specific subscription ordered by priority + */ + List findBySubscriptionIdAndEnabledTrueOrderByPriorityAsc(UUID subscriptionId); + + /** + * Find rules by name pattern + */ + List findByNameContainingIgnoreCase(String namePattern); + + /** + * Find rules for a subscription by name pattern + */ + List findBySubscriptionIdAndNameContainingIgnoreCase(UUID subscriptionId, String namePattern); + + /** + * Check if a rule with the given name already exists for a subscription + */ + boolean existsBySubscriptionIdAndName(UUID subscriptionId, String name); + + /** + * Find rules by enabled status + */ + List findByEnabled(Boolean enabled); + + /** + * Find rules with a specific priority + */ + List findByPriority(Integer priority); + + /** + * Find rules created by a specific user + */ + List findByCreatedByUserId(UUID createdByUserId); + + /** + * Count rules for a specific subscription + */ + long countBySubscriptionId(UUID subscriptionId); + + /** + * Count enabled rules for a specific subscription + */ + long countBySubscriptionIdAndEnabledTrue(UUID subscriptionId); + + /** + * Find rules that contain a specific condition pattern + */ + @Query("SELECT er FROM EventRule er WHERE er.condition LIKE %:conditionPattern%") + List findByConditionContaining(@Param("conditionPattern") String conditionPattern); + + /** + * Find the highest priority rule for a subscription + */ + @Query("SELECT er FROM EventRule er WHERE er.subscription.id = :subscriptionId AND er.enabled = true ORDER BY er.priority ASC LIMIT 1") + EventRule findHighestPriorityEnabledRule(@Param("subscriptionId") UUID subscriptionId); + + /** + * Find rules for a subscription ordered by priority + */ + List findBySubscriptionIdOrderByPriorityAsc(UUID subscriptionId); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/repository/EventSubscriptionRepository.java b/src/main/java/com/dalab/policyengine/repository/EventSubscriptionRepository.java index d2d86d7311fef30d3a8b72bc72cf8c976dde1874..01dd0f6a716b06d43c1d7083cf61a7e8de49772d 100644 --- a/src/main/java/com/dalab/policyengine/repository/EventSubscriptionRepository.java +++ b/src/main/java/com/dalab/policyengine/repository/EventSubscriptionRepository.java @@ -1,100 +1,100 @@ -package com.dalab.policyengine.repository; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import com.dalab.policyengine.model.EventSubscription; -import com.dalab.policyengine.model.EventSubscriptionStatus; -import com.dalab.policyengine.model.EventType; - -/** - * Repository interface for EventSubscription entities. - * Provides data access methods for event subscription management. - */ -@Repository -public interface EventSubscriptionRepository extends JpaRepository { - - /** - * Find all event subscriptions for a specific user - */ - List findByUserId(UUID userId); - - /** - * Find event subscriptions by user ID with pagination - */ - Page findByUserId(UUID userId, Pageable pageable); - - /** - * Find event subscriptions by status - */ - List findByStatus(EventSubscriptionStatus status); - - /** - * Find active event subscriptions for a specific user - */ - List findByUserIdAndStatus(UUID userId, EventSubscriptionStatus status); - - /** - * Find event subscriptions that monitor a specific event type - */ - @Query("SELECT es FROM EventSubscription es JOIN es.eventTypes et WHERE et = :eventType AND es.status = 'ACTIVE'") - List findActiveSubscriptionsByEventType(@Param("eventType") EventType eventType); - - /** - * Find event subscriptions that monitor a specific source service - */ - @Query("SELECT es FROM EventSubscription es JOIN es.sourceServices ss WHERE ss = :sourceService AND es.status = 'ACTIVE'") - List findActiveSubscriptionsBySourceService(@Param("sourceService") String sourceService); - - /** - * Find event subscriptions by name pattern - */ - List findByNameContainingIgnoreCase(String namePattern); - - /** - * Find event subscriptions for a user by name pattern - */ - List findByUserIdAndNameContainingIgnoreCase(UUID userId, String namePattern); - - /** - * Check if a subscription with the given name already exists for a user - */ - boolean existsByUserIdAndName(UUID userId, String name); - - /** - * Find active subscriptions that could match an event based on event type and source service - */ - @Query("SELECT DISTINCT es FROM EventSubscription es " + - "LEFT JOIN es.eventTypes et " + - "LEFT JOIN es.sourceServices ss " + - "WHERE es.status = 'ACTIVE' " + - "AND (et = :eventType OR es.eventTypes IS EMPTY) " + - "AND (ss = :sourceService OR es.sourceServices IS EMPTY)") - List findPotentialMatchingSubscriptions( - @Param("eventType") EventType eventType, - @Param("sourceService") String sourceService); - - /** - * Count active subscriptions for a user - */ - long countByUserIdAndStatus(UUID userId, EventSubscriptionStatus status); - - /** - * Find subscriptions created by a specific user - */ - List findByCreatedByUserId(UUID createdByUserId); - - /** - * Find subscriptions with a specific notification type enabled - */ - @Query("SELECT es FROM EventSubscription es WHERE jsonb_extract_path_text(CAST(es.notificationConfig AS text), :notificationType) = 'true'") - List findSubscriptionsWithNotificationType(@Param("notificationType") String notificationType); +package com.dalab.policyengine.repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.dalab.policyengine.model.EventSubscription; +import com.dalab.policyengine.model.EventSubscriptionStatus; +import com.dalab.policyengine.model.EventType; + +/** + * Repository interface for EventSubscription entities. + * Provides data access methods for event subscription management. + */ +@Repository +public interface EventSubscriptionRepository extends JpaRepository { + + /** + * Find all event subscriptions for a specific user + */ + List findByUserId(UUID userId); + + /** + * Find event subscriptions by user ID with pagination + */ + Page findByUserId(UUID userId, Pageable pageable); + + /** + * Find event subscriptions by status + */ + List findByStatus(EventSubscriptionStatus status); + + /** + * Find active event subscriptions for a specific user + */ + List findByUserIdAndStatus(UUID userId, EventSubscriptionStatus status); + + /** + * Find event subscriptions that monitor a specific event type + */ + @Query("SELECT es FROM EventSubscription es JOIN es.eventTypes et WHERE et = :eventType AND es.status = 'ACTIVE'") + List findActiveSubscriptionsByEventType(@Param("eventType") EventType eventType); + + /** + * Find event subscriptions that monitor a specific source service + */ + @Query("SELECT es FROM EventSubscription es JOIN es.sourceServices ss WHERE ss = :sourceService AND es.status = 'ACTIVE'") + List findActiveSubscriptionsBySourceService(@Param("sourceService") String sourceService); + + /** + * Find event subscriptions by name pattern + */ + List findByNameContainingIgnoreCase(String namePattern); + + /** + * Find event subscriptions for a user by name pattern + */ + List findByUserIdAndNameContainingIgnoreCase(UUID userId, String namePattern); + + /** + * Check if a subscription with the given name already exists for a user + */ + boolean existsByUserIdAndName(UUID userId, String name); + + /** + * Find active subscriptions that could match an event based on event type and source service + */ + @Query("SELECT DISTINCT es FROM EventSubscription es " + + "LEFT JOIN es.eventTypes et " + + "LEFT JOIN es.sourceServices ss " + + "WHERE es.status = 'ACTIVE' " + + "AND (et = :eventType OR es.eventTypes IS EMPTY) " + + "AND (ss = :sourceService OR es.sourceServices IS EMPTY)") + List findPotentialMatchingSubscriptions( + @Param("eventType") EventType eventType, + @Param("sourceService") String sourceService); + + /** + * Count active subscriptions for a user + */ + long countByUserIdAndStatus(UUID userId, EventSubscriptionStatus status); + + /** + * Find subscriptions created by a specific user + */ + List findByCreatedByUserId(UUID createdByUserId); + + /** + * Find subscriptions with a specific notification type enabled + */ + @Query("SELECT es FROM EventSubscription es WHERE jsonb_extract_path_text(CAST(es.notificationConfig AS text), :notificationType) = 'true'") + List findSubscriptionsWithNotificationType(@Param("notificationType") String notificationType); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/repository/PolicyDraftRepository.java b/src/main/java/com/dalab/policyengine/repository/PolicyDraftRepository.java index d9a1aaa2de395c8634625d259e23303792e4295b..5c59100a0be2f43bc0b1ec57aba940fcfff04ed1 100644 --- a/src/main/java/com/dalab/policyengine/repository/PolicyDraftRepository.java +++ b/src/main/java/com/dalab/policyengine/repository/PolicyDraftRepository.java @@ -1,169 +1,169 @@ -package com.dalab.policyengine.repository; - -import java.time.Instant; -import java.util.List; -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import com.dalab.policyengine.model.PolicyDraft; -import com.dalab.policyengine.model.PolicyDraftStatus; - -/** - * Repository interface for PolicyDraft entities. - * Provides comprehensive query methods for draft management and workflow operations. - */ -@Repository -public interface PolicyDraftRepository extends JpaRepository, JpaSpecificationExecutor { - - /** - * Find all drafts by their current status. - */ - Page findByStatus(PolicyDraftStatus status, Pageable pageable); - - /** - * Find all drafts created by a specific user. - */ - Page findByCreatedByUserId(UUID createdByUserId, Pageable pageable); - - /** - * Find all drafts where the user is a stakeholder. - */ - @Query("SELECT pd FROM PolicyDraft pd WHERE :userId MEMBER OF pd.stakeholders") - Page findByStakeholder(@Param("userId") UUID userId, Pageable pageable); - - /** - * Find all drafts pending review (submitted or under review). - */ - @Query("SELECT pd FROM PolicyDraft pd WHERE pd.status IN ('SUBMITTED', 'UNDER_REVIEW')") - Page findPendingReview(Pageable pageable); - - /** - * Find all drafts that require attention from a specific user (created by them or they are stakeholder). - */ - @Query("SELECT DISTINCT pd FROM PolicyDraft pd WHERE pd.createdByUserId = :userId OR :userId MEMBER OF pd.stakeholders") - Page findRequiringAttention(@Param("userId") UUID userId, Pageable pageable); - - /** - * Find drafts by name containing specific text (case insensitive). - */ - Page findByNameContainingIgnoreCase(String name, Pageable pageable); - - /** - * Find drafts by category. - */ - Page findByCategory(String category, Pageable pageable); - - /** - * Find drafts by priority level. - */ - Page findByPriority(String priority, Pageable pageable); - - /** - * Find drafts that are based on a specific policy (updates to existing policies). - */ - List findByBasePolicyId(UUID basePolicyId); - - /** - * Find the latest version of drafts for a specific base policy. - */ - @Query("SELECT pd FROM PolicyDraft pd WHERE pd.basePolicyId = :basePolicyId ORDER BY pd.version DESC") - List findLatestVersionByBasePolicyId(@Param("basePolicyId") UUID basePolicyId); - - /** - * Find drafts with target implementation date before specified date. - */ - List findByTargetImplementationDateBefore(Instant date); - - /** - * Find drafts created within a specific time range. - */ - Page findByCreatedAtBetween(Instant startDate, Instant endDate, Pageable pageable); - - /** - * Find drafts that have been in a specific status for longer than specified time. - */ - @Query("SELECT pd FROM PolicyDraft pd WHERE pd.status = :status AND pd.updatedAt < :cutoffTime") - List findStaleInStatus(@Param("status") PolicyDraftStatus status, @Param("cutoffTime") Instant cutoffTime); - - /** - * Find drafts containing specific tags. - */ - @Query("SELECT pd FROM PolicyDraft pd WHERE :tag MEMBER OF pd.tags") - Page findByTag(@Param("tag") String tag, Pageable pageable); - - /** - * Find drafts by multiple criteria (complex search). - */ - @Query("SELECT pd FROM PolicyDraft pd WHERE " + - "(:status IS NULL OR pd.status = :status) AND " + - "(:category IS NULL OR pd.category = :category) AND " + - "(:priority IS NULL OR pd.priority = :priority) AND " + - "(:createdBy IS NULL OR pd.createdByUserId = :createdBy) AND " + - "(:nameFilter IS NULL OR LOWER(pd.name) LIKE LOWER(CONCAT('%', :nameFilter, '%')))") - Page findByCriteria( - @Param("status") PolicyDraftStatus status, - @Param("category") String category, - @Param("priority") String priority, - @Param("createdBy") UUID createdBy, - @Param("nameFilter") String nameFilter, - Pageable pageable - ); - - /** - * Count drafts by status. - */ - long countByStatus(PolicyDraftStatus status); - - /** - * Count drafts created by a specific user. - */ - long countByCreatedByUserId(UUID createdByUserId); - - /** - * Check if a draft name already exists (for validation). - */ - boolean existsByName(String name); - - /** - * Check if a draft name exists excluding a specific draft (for updates). - */ - @Query("SELECT CASE WHEN COUNT(pd) > 0 THEN true ELSE false END FROM PolicyDraft pd WHERE pd.name = :name AND pd.id != :excludeId") - boolean existsByNameExcludingId(@Param("name") String name, @Param("excludeId") UUID excludeId); - - /** - * Find all distinct categories used in drafts. - */ - @Query("SELECT DISTINCT pd.category FROM PolicyDraft pd WHERE pd.category IS NOT NULL") - List findAllCategories(); - - /** - * Find all distinct tags used in drafts. - */ - @Query("SELECT DISTINCT tag FROM PolicyDraft pd JOIN pd.tags tag") - List findAllTags(); - - /** - * Get draft statistics for dashboard. - */ - @Query("SELECT pd.status, COUNT(pd) FROM PolicyDraft pd GROUP BY pd.status") - List getDraftStatisticsByStatus(); - - /** - * Get drafts summary by user. - */ - @Query("SELECT pd.createdByUserId, pd.status, COUNT(pd) FROM PolicyDraft pd GROUP BY pd.createdByUserId, pd.status") - List getDraftStatisticsByUser(); - - /** - * Find overdue drafts (target implementation date passed and still not published). - */ - @Query("SELECT pd FROM PolicyDraft pd WHERE pd.targetImplementationDate < :currentDate AND pd.status != 'PUBLISHED' AND pd.status != 'REJECTED' AND pd.status != 'ARCHIVED'") - List findOverdueDrafts(@Param("currentDate") Instant currentDate); +package com.dalab.policyengine.repository; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.dalab.policyengine.model.PolicyDraft; +import com.dalab.policyengine.model.PolicyDraftStatus; + +/** + * Repository interface for PolicyDraft entities. + * Provides comprehensive query methods for draft management and workflow operations. + */ +@Repository +public interface PolicyDraftRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * Find all drafts by their current status. + */ + Page findByStatus(PolicyDraftStatus status, Pageable pageable); + + /** + * Find all drafts created by a specific user. + */ + Page findByCreatedByUserId(UUID createdByUserId, Pageable pageable); + + /** + * Find all drafts where the user is a stakeholder. + */ + @Query("SELECT pd FROM PolicyDraft pd WHERE :userId MEMBER OF pd.stakeholders") + Page findByStakeholder(@Param("userId") UUID userId, Pageable pageable); + + /** + * Find all drafts pending review (submitted or under review). + */ + @Query("SELECT pd FROM PolicyDraft pd WHERE pd.status IN ('SUBMITTED', 'UNDER_REVIEW')") + Page findPendingReview(Pageable pageable); + + /** + * Find all drafts that require attention from a specific user (created by them or they are stakeholder). + */ + @Query("SELECT DISTINCT pd FROM PolicyDraft pd WHERE pd.createdByUserId = :userId OR :userId MEMBER OF pd.stakeholders") + Page findRequiringAttention(@Param("userId") UUID userId, Pageable pageable); + + /** + * Find drafts by name containing specific text (case insensitive). + */ + Page findByNameContainingIgnoreCase(String name, Pageable pageable); + + /** + * Find drafts by category. + */ + Page findByCategory(String category, Pageable pageable); + + /** + * Find drafts by priority level. + */ + Page findByPriority(String priority, Pageable pageable); + + /** + * Find drafts that are based on a specific policy (updates to existing policies). + */ + List findByBasePolicyId(UUID basePolicyId); + + /** + * Find the latest version of drafts for a specific base policy. + */ + @Query("SELECT pd FROM PolicyDraft pd WHERE pd.basePolicyId = :basePolicyId ORDER BY pd.version DESC") + List findLatestVersionByBasePolicyId(@Param("basePolicyId") UUID basePolicyId); + + /** + * Find drafts with target implementation date before specified date. + */ + List findByTargetImplementationDateBefore(Instant date); + + /** + * Find drafts created within a specific time range. + */ + Page findByCreatedAtBetween(Instant startDate, Instant endDate, Pageable pageable); + + /** + * Find drafts that have been in a specific status for longer than specified time. + */ + @Query("SELECT pd FROM PolicyDraft pd WHERE pd.status = :status AND pd.updatedAt < :cutoffTime") + List findStaleInStatus(@Param("status") PolicyDraftStatus status, @Param("cutoffTime") Instant cutoffTime); + + /** + * Find drafts containing specific tags. + */ + @Query("SELECT pd FROM PolicyDraft pd WHERE :tag MEMBER OF pd.tags") + Page findByTag(@Param("tag") String tag, Pageable pageable); + + /** + * Find drafts by multiple criteria (complex search). + */ + @Query("SELECT pd FROM PolicyDraft pd WHERE " + + "(:status IS NULL OR pd.status = :status) AND " + + "(:category IS NULL OR pd.category = :category) AND " + + "(:priority IS NULL OR pd.priority = :priority) AND " + + "(:createdBy IS NULL OR pd.createdByUserId = :createdBy) AND " + + "(:nameFilter IS NULL OR LOWER(pd.name) LIKE LOWER(CONCAT('%', :nameFilter, '%')))") + Page findByCriteria( + @Param("status") PolicyDraftStatus status, + @Param("category") String category, + @Param("priority") String priority, + @Param("createdBy") UUID createdBy, + @Param("nameFilter") String nameFilter, + Pageable pageable + ); + + /** + * Count drafts by status. + */ + long countByStatus(PolicyDraftStatus status); + + /** + * Count drafts created by a specific user. + */ + long countByCreatedByUserId(UUID createdByUserId); + + /** + * Check if a draft name already exists (for validation). + */ + boolean existsByName(String name); + + /** + * Check if a draft name exists excluding a specific draft (for updates). + */ + @Query("SELECT CASE WHEN COUNT(pd) > 0 THEN true ELSE false END FROM PolicyDraft pd WHERE pd.name = :name AND pd.id != :excludeId") + boolean existsByNameExcludingId(@Param("name") String name, @Param("excludeId") UUID excludeId); + + /** + * Find all distinct categories used in drafts. + */ + @Query("SELECT DISTINCT pd.category FROM PolicyDraft pd WHERE pd.category IS NOT NULL") + List findAllCategories(); + + /** + * Find all distinct tags used in drafts. + */ + @Query("SELECT DISTINCT tag FROM PolicyDraft pd JOIN pd.tags tag") + List findAllTags(); + + /** + * Get draft statistics for dashboard. + */ + @Query("SELECT pd.status, COUNT(pd) FROM PolicyDraft pd GROUP BY pd.status") + List getDraftStatisticsByStatus(); + + /** + * Get drafts summary by user. + */ + @Query("SELECT pd.createdByUserId, pd.status, COUNT(pd) FROM PolicyDraft pd GROUP BY pd.createdByUserId, pd.status") + List getDraftStatisticsByUser(); + + /** + * Find overdue drafts (target implementation date passed and still not published). + */ + @Query("SELECT pd FROM PolicyDraft pd WHERE pd.targetImplementationDate < :currentDate AND pd.status != 'PUBLISHED' AND pd.status != 'REJECTED' AND pd.status != 'ARCHIVED'") + List findOverdueDrafts(@Param("currentDate") Instant currentDate); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/repository/PolicyEvaluationRepository.java b/src/main/java/com/dalab/policyengine/repository/PolicyEvaluationRepository.java index f1b0efe382db41ec443e868965b21620c4f5ebdc..c84b9a6ae0f6b247cc691ccbaa4d8470e100d038 100644 --- a/src/main/java/com/dalab/policyengine/repository/PolicyEvaluationRepository.java +++ b/src/main/java/com/dalab/policyengine/repository/PolicyEvaluationRepository.java @@ -1,20 +1,20 @@ -package com.dalab.policyengine.repository; - -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.stereotype.Repository; - -import com.dalab.policyengine.model.PolicyEvaluation; -import com.dalab.policyengine.model.PolicyEvaluationStatus; - -@Repository -public interface PolicyEvaluationRepository extends JpaRepository, JpaSpecificationExecutor { - Page findByPolicyId(UUID policyId, Pageable pageable); - Page findByTargetAssetId(String targetAssetId, Pageable pageable); - Page findByStatus(PolicyEvaluationStatus status, Pageable pageable); - Page findByPolicyIdAndTargetAssetId(UUID policyId, String targetAssetId, Pageable pageable); +package com.dalab.policyengine.repository; + +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +import com.dalab.policyengine.model.PolicyEvaluation; +import com.dalab.policyengine.model.PolicyEvaluationStatus; + +@Repository +public interface PolicyEvaluationRepository extends JpaRepository, JpaSpecificationExecutor { + Page findByPolicyId(UUID policyId, Pageable pageable); + Page findByTargetAssetId(String targetAssetId, Pageable pageable); + Page findByStatus(PolicyEvaluationStatus status, Pageable pageable); + Page findByPolicyIdAndTargetAssetId(UUID policyId, String targetAssetId, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/repository/PolicyRepository.java b/src/main/java/com/dalab/policyengine/repository/PolicyRepository.java index 4c4877ebcdf9cdc39c9b72be1edbeec54bea65c6..9c4b0512a68f04123dcd8af1ba1a7cbca507f3b9 100644 --- a/src/main/java/com/dalab/policyengine/repository/PolicyRepository.java +++ b/src/main/java/com/dalab/policyengine/repository/PolicyRepository.java @@ -1,18 +1,18 @@ -package com.dalab.policyengine.repository; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.stereotype.Repository; - -import com.dalab.policyengine.model.Policy; -import com.dalab.policyengine.model.PolicyStatus; - -@Repository -public interface PolicyRepository extends JpaRepository, JpaSpecificationExecutor { - Optional findByName(String name); - List findByStatus(PolicyStatus status); +package com.dalab.policyengine.repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +import com.dalab.policyengine.model.Policy; +import com.dalab.policyengine.model.PolicyStatus; + +@Repository +public interface PolicyRepository extends JpaRepository, JpaSpecificationExecutor { + Optional findByName(String name); + List findByStatus(PolicyStatus status); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/repository/PolicyRuleRepository.java b/src/main/java/com/dalab/policyengine/repository/PolicyRuleRepository.java index 2b2d6046e67b16754dc86303b98b9d72fbed8151..a7b94da3f9d309645ce7a0f2f443776f9940382c 100644 --- a/src/main/java/com/dalab/policyengine/repository/PolicyRuleRepository.java +++ b/src/main/java/com/dalab/policyengine/repository/PolicyRuleRepository.java @@ -1,14 +1,14 @@ -package com.dalab.policyengine.repository; - -import java.util.List; -import java.util.UUID; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import com.dalab.policyengine.model.PolicyRule; - -@Repository -public interface PolicyRuleRepository extends JpaRepository { - List findByPolicyId(UUID policyId); +package com.dalab.policyengine.repository; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.dalab.policyengine.model.PolicyRule; + +@Repository +public interface PolicyRuleRepository extends JpaRepository { + List findByPolicyId(UUID policyId); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/service/EventSubscriptionService.java b/src/main/java/com/dalab/policyengine/service/EventSubscriptionService.java index f837e5d4b38bfef3c17b6cc93934d648db0235dd..38de151a63e54ee0c7cd30422d61bb808e2522a1 100644 --- a/src/main/java/com/dalab/policyengine/service/EventSubscriptionService.java +++ b/src/main/java/com/dalab/policyengine/service/EventSubscriptionService.java @@ -1,627 +1,627 @@ -package com.dalab.policyengine.service; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.jeasy.rules.api.Facts; -import org.jeasy.rules.api.Rule; -import org.jeasy.rules.api.Rules; -import org.jeasy.rules.api.RulesEngine; -import org.jeasy.rules.core.DefaultRulesEngine; -import org.jeasy.rules.mvel.MVELRule; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; - -import com.dalab.policyengine.common.ResourceNotFoundException; -import com.dalab.policyengine.dto.EventAnalyticsDTO; -import com.dalab.policyengine.dto.EventStreamDTO; -import com.dalab.policyengine.dto.EventSubscriptionInputDTO; -import com.dalab.policyengine.dto.EventSubscriptionOutputDTO; -import com.dalab.policyengine.model.EventRule; -import com.dalab.policyengine.model.EventSubscription; -import com.dalab.policyengine.model.EventSubscriptionStatus; -import com.dalab.policyengine.model.EventType; -import com.dalab.policyengine.repository.EventRuleRepository; -import com.dalab.policyengine.repository.EventSubscriptionRepository; -import com.fasterxml.jackson.databind.ObjectMapper; - -import jakarta.persistence.criteria.Predicate; - -/** - * Service implementation for Event Subscription management and event streaming. - * Provides operations for creating, managing, and processing event subscriptions. - */ -@Service -@Transactional -public class EventSubscriptionService implements IEventSubscriptionService { - - private static final Logger log = LoggerFactory.getLogger(EventSubscriptionService.class); - - private final EventSubscriptionRepository eventSubscriptionRepository; - private final EventRuleRepository eventRuleRepository; - private final ObjectMapper objectMapper; - private final RulesEngine rulesEngine; - - @Autowired - public EventSubscriptionService(EventSubscriptionRepository eventSubscriptionRepository, - EventRuleRepository eventRuleRepository, - ObjectMapper objectMapper) { - this.eventSubscriptionRepository = eventSubscriptionRepository; - this.eventRuleRepository = eventRuleRepository; - this.objectMapper = objectMapper; - this.rulesEngine = new DefaultRulesEngine(); - } - - // ==================== SUBSCRIPTION MANAGEMENT ==================== - - @Override - public EventSubscriptionOutputDTO createSubscription(EventSubscriptionInputDTO inputDTO, UUID creatorUserId) { - log.info("Creating event subscription for user: {}", creatorUserId); - - // Validate input - validateSubscriptionConfiguration(inputDTO); - - // Create subscription entity - EventSubscription subscription = new EventSubscription(); - subscription.setName(inputDTO.getName()); - subscription.setDescription(inputDTO.getDescription()); - subscription.setUserId(creatorUserId); - subscription.setStatus(EventSubscriptionStatus.ACTIVE); - subscription.setNotificationChannels(inputDTO.getNotificationChannels()); - subscription.setNotificationSettings(inputDTO.getNotificationSettings()); - subscription.setCreatedAt(Instant.now()); - subscription.setUpdatedAt(Instant.now()); - subscription.setCreatedByUserId(creatorUserId); - subscription.setUpdatedByUserId(creatorUserId); - - // Save subscription - EventSubscription savedSubscription = eventSubscriptionRepository.save(subscription); - - // Create and save rules - if (inputDTO.getRules() != null && !inputDTO.getRules().isEmpty()) { - List rules = inputDTO.getRules().stream() - .map(ruleInput -> { - EventRule rule = new EventRule(); - rule.setSubscriptionId(savedSubscription.getId()); - rule.setCondition(ruleInput.getCondition()); - rule.setEventTypes(ruleInput.getEventTypes()); - rule.setSeverityLevels(ruleInput.getSeverityLevels()); - rule.setSourceServices(ruleInput.getSourceServices()); - rule.setCreatedAt(Instant.now()); - return eventRuleRepository.save(rule); - }) - .collect(Collectors.toList()); - - savedSubscription.setRules(rules); - } - - log.info("Successfully created event subscription: {}", savedSubscription.getId()); - return convertToOutputDTO(savedSubscription); - } - - @Override - public EventSubscriptionOutputDTO updateSubscription(UUID subscriptionId, EventSubscriptionInputDTO inputDTO, UUID updaterUserId) { - log.info("Updating event subscription: {}", subscriptionId); - - EventSubscription subscription = eventSubscriptionRepository.findById(subscriptionId) - .orElseThrow(() -> new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString())); - - // Validate input - validateSubscriptionConfiguration(inputDTO); - - // Update subscription fields - subscription.setName(inputDTO.getName()); - subscription.setDescription(inputDTO.getDescription()); - subscription.setNotificationChannels(inputDTO.getNotificationChannels()); - subscription.setNotificationSettings(inputDTO.getNotificationSettings()); - subscription.setUpdatedAt(Instant.now()); - subscription.setUpdatedByUserId(updaterUserId); - - // Update rules - if (inputDTO.getRules() != null) { - // Delete existing rules - eventRuleRepository.deleteBySubscriptionId(subscriptionId); - - // Create new rules - List rules = inputDTO.getRules().stream() - .map(ruleInput -> { - EventRule rule = new EventRule(); - rule.setSubscriptionId(subscriptionId); - rule.setCondition(ruleInput.getCondition()); - rule.setEventTypes(ruleInput.getEventTypes()); - rule.setSeverityLevels(ruleInput.getSeverityLevels()); - rule.setSourceServices(ruleInput.getSourceServices()); - rule.setCreatedAt(Instant.now()); - return eventRuleRepository.save(rule); - }) - .collect(Collectors.toList()); - - subscription.setRules(rules); - } - - EventSubscription updatedSubscription = eventSubscriptionRepository.save(subscription); - log.info("Successfully updated event subscription: {}", subscriptionId); - return convertToOutputDTO(updatedSubscription); - } - - @Override - @Transactional(readOnly = true) - public EventSubscriptionOutputDTO getSubscriptionById(UUID subscriptionId) { - EventSubscription subscription = eventSubscriptionRepository.findById(subscriptionId) - .orElseThrow(() -> new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString())); - return convertToOutputDTO(subscription); - } - - @Override - @Transactional(readOnly = true) - public Page getSubscriptionsForUser(UUID userId, Pageable pageable, String status, String nameContains) { - Specification spec = (root, query, criteriaBuilder) -> { - List predicates = new ArrayList<>(); - predicates.add(criteriaBuilder.equal(root.get("userId"), userId)); - - if (StringUtils.hasText(status)) { - try { - predicates.add(criteriaBuilder.equal(root.get("status"), EventSubscriptionStatus.valueOf(status.toUpperCase()))); - } catch (IllegalArgumentException e) { - log.warn("Invalid status provided for filtering: {}", status); - predicates.add(criteriaBuilder.disjunction()); - } - } - - if (StringUtils.hasText(nameContains)) { - predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), - "%" + nameContains.toLowerCase() + "%")); - } - - return criteriaBuilder.and(predicates.toArray(new Predicate[0])); - }; - - Page subscriptionPage = eventSubscriptionRepository.findAll(spec, pageable); - return subscriptionPage.map(this::convertToOutputDTO); - } - - @Override - @Transactional(readOnly = true) - public Page getAllSubscriptions(Pageable pageable, String status, String nameContains) { - Specification spec = (root, query, criteriaBuilder) -> { - List predicates = new ArrayList<>(); - - if (StringUtils.hasText(status)) { - try { - predicates.add(criteriaBuilder.equal(root.get("status"), EventSubscriptionStatus.valueOf(status.toUpperCase()))); - } catch (IllegalArgumentException e) { - log.warn("Invalid status provided for filtering: {}", status); - predicates.add(criteriaBuilder.disjunction()); - } - } - - if (StringUtils.hasText(nameContains)) { - predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), - "%" + nameContains.toLowerCase() + "%")); - } - - return criteriaBuilder.and(predicates.toArray(new Predicate[0])); - }; - - Page subscriptionPage = eventSubscriptionRepository.findAll(spec, pageable); - return subscriptionPage.map(this::convertToOutputDTO); - } - - @Override - public void deleteSubscription(UUID subscriptionId) { - if (!eventSubscriptionRepository.existsById(subscriptionId)) { - throw new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString()); - } - - // Delete associated rules first - eventRuleRepository.deleteBySubscriptionId(subscriptionId); - - // Delete subscription - eventSubscriptionRepository.deleteById(subscriptionId); - log.info("Successfully deleted event subscription: {}", subscriptionId); - } - - @Override - public EventSubscriptionOutputDTO updateSubscriptionStatus(UUID subscriptionId, EventSubscriptionStatus status, UUID updaterUserId) { - EventSubscription subscription = eventSubscriptionRepository.findById(subscriptionId) - .orElseThrow(() -> new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString())); - - subscription.setStatus(status); - subscription.setUpdatedAt(Instant.now()); - subscription.setUpdatedByUserId(updaterUserId); - - EventSubscription updatedSubscription = eventSubscriptionRepository.save(subscription); - log.info("Updated subscription {} status to: {}", subscriptionId, status); - return convertToOutputDTO(updatedSubscription); - } - - // ==================== EVENT STREAMING AND PROCESSING ==================== - - @Override - @Transactional(readOnly = true) - public List getEventStreamForUser(UUID userId, Integer limit) { - log.info("Getting event stream for user: {} with limit: {}", userId, limit); - - // Get user's active subscriptions - List activeSubscriptions = eventSubscriptionRepository - .findByUserIdAndStatus(userId, EventSubscriptionStatus.ACTIVE); - - if (activeSubscriptions.isEmpty()) { - return new ArrayList<>(); - } - - // For now, return mock events that would match user's subscriptions - // In a real implementation, this would query actual event streams - return generateMockEventsForSubscriptions(activeSubscriptions, limit != null ? limit : 50); - } - - @Override - @Transactional(readOnly = true) - public List getAllEventStream(Integer limit) { - log.info("Getting all event stream with limit: {}", limit); - - // Return mock events for all subscriptions - List allSubscriptions = eventSubscriptionRepository.findAll(); - return generateMockEventsForSubscriptions(allSubscriptions, limit != null ? limit : 100); - } - - @Override - public void processIncomingEvent(EventStreamDTO eventDTO) { - log.info("Processing incoming event: {} from service: {}", eventDTO.getEventType(), eventDTO.getSourceService()); - - // Find all active subscriptions that might match this event - List activeSubscriptions = eventSubscriptionRepository - .findByStatus(EventSubscriptionStatus.ACTIVE); - - for (EventSubscription subscription : activeSubscriptions) { - try { - if (matchesSubscription(eventDTO, subscription)) { - log.info("Event matches subscription: {}", subscription.getId()); - // In a real implementation, this would trigger notifications - // For now, just log the match - } - } catch (Exception e) { - log.error("Error processing event for subscription {}: {}", subscription.getId(), e.getMessage(), e); - } - } - } - - @Override - @Transactional(readOnly = true) - public Page getHistoricalEventsForSubscription(UUID subscriptionId, Pageable pageable) { - // Verify subscription exists - if (!eventSubscriptionRepository.existsById(subscriptionId)) { - throw new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString()); - } - - // For now, return mock historical events - // In a real implementation, this would query actual event history - List mockEvents = generateMockHistoricalEvents(subscriptionId, pageable.getPageSize()); - - // Create a simple page implementation - return new org.springframework.data.domain.PageImpl<>( - mockEvents, - pageable, - mockEvents.size() - ); - } - - // ==================== ANALYTICS AND METRICS ==================== - - @Override - @Transactional(readOnly = true) - public EventAnalyticsDTO getEventAnalyticsForUser(UUID userId) { - log.info("Getting event analytics for user: {}", userId); - - List userSubscriptions = eventSubscriptionRepository.findByUserId(userId); - - EventAnalyticsDTO analytics = new EventAnalyticsDTO(); - analytics.setUserId(userId); - analytics.setTotalSubscriptions(userSubscriptions.size()); - analytics.setActiveSubscriptions((int) userSubscriptions.stream() - .filter(s -> s.getStatus() == EventSubscriptionStatus.ACTIVE) - .count()); - - // Add mock metrics - EventAnalyticsDTO.EventMetricsDTO metrics = new EventAnalyticsDTO.EventMetricsDTO(); - metrics.setTotalEvents(1250); - metrics.setEventsToday(45); - metrics.setEventsThisWeek(320); - metrics.setEventsThisMonth(1250); - analytics.setEventMetrics(metrics); - - // Add mock trends - EventAnalyticsDTO.EventTrendsDTO trends = new EventAnalyticsDTO.EventTrendsDTO(); - trends.setTrendDirection("increasing"); - trends.setTrendPercentage(12.5); - trends.setPeakHour("14:00"); - trends.setPeakDay("Wednesday"); - analytics.setEventTrends(trends); - - return analytics; - } - - @Override - @Transactional(readOnly = true) - public EventAnalyticsDTO getSystemEventAnalytics() { - log.info("Getting system-wide event analytics"); - - List allSubscriptions = eventSubscriptionRepository.findAll(); - - EventAnalyticsDTO analytics = new EventAnalyticsDTO(); - analytics.setTotalSubscriptions(allSubscriptions.size()); - analytics.setActiveSubscriptions((int) allSubscriptions.stream() - .filter(s -> s.getStatus() == EventSubscriptionStatus.ACTIVE) - .count()); - - // Add comprehensive system metrics - EventAnalyticsDTO.EventMetricsDTO metrics = new EventAnalyticsDTO.EventMetricsDTO(); - metrics.setTotalEvents(8750); - metrics.setEventsToday(245); - metrics.setEventsThisWeek(1820); - metrics.setEventsThisMonth(8750); - analytics.setEventMetrics(metrics); - - // Add system trends - EventAnalyticsDTO.EventTrendsDTO trends = new EventAnalyticsDTO.EventTrendsDTO(); - trends.setTrendDirection("stable"); - trends.setTrendPercentage(2.1); - trends.setPeakHour("10:00"); - trends.setPeakDay("Tuesday"); - analytics.setEventTrends(trends); - - return analytics; - } - - @Override - @Transactional(readOnly = true) - public EventAnalyticsDTO getEventAnalyticsForTimeRange(UUID userId, Instant fromTime, Instant toTime) { - log.info("Getting event analytics for user: {} from {} to {}", userId, fromTime, toTime); - - // For now, return mock analytics for the time range - // In a real implementation, this would query actual event data - EventAnalyticsDTO analytics = new EventAnalyticsDTO(); - analytics.setUserId(userId); - analytics.setTotalSubscriptions(5); - analytics.setActiveSubscriptions(3); - - EventAnalyticsDTO.EventMetricsDTO metrics = new EventAnalyticsDTO.EventMetricsDTO(); - metrics.setTotalEvents(450); - metrics.setEventsToday(25); - metrics.setEventsThisWeek(180); - metrics.setEventsThisMonth(450); - analytics.setEventMetrics(metrics); - - return analytics; - } - - // ==================== UTILITY METHODS ==================== - - @Override - public boolean testEventRule(String ruleCondition, EventStreamDTO sampleEvent) { - try { - // Create MVEL rule for testing - Rule testRule = new MVELRule() - .name("Test Rule") - .description("Test rule for validation") - .when(ruleCondition) - .then("true"); - - Rules rules = new Rules(testRule); - Facts facts = new Facts(); - - // Add event data to facts - facts.put("event", sampleEvent); - facts.put("eventType", sampleEvent.getEventType()); - facts.put("severity", sampleEvent.getSeverity()); - facts.put("sourceService", sampleEvent.getSourceService()); - facts.put("assetId", sampleEvent.getAssetId()); - facts.put("timestamp", sampleEvent.getTimestamp()); - - // Execute rule - rulesEngine.fire(rules, facts); - - // Check if rule was triggered - return facts.get("true") != null; - - } catch (Exception e) { - log.error("Error testing event rule: {}", e.getMessage(), e); - return false; - } - } - - @Override - public List getAvailableEventTypes() { - return Arrays.asList(EventType.values()); - } - - @Override - public List getAvailableSourceServices() { - return Arrays.asList( - "da-discovery", - "da-catalog", - "da-policyengine", - "da-reporting", - "da-autolabel", - "da-autoarchival", - "da-autodelete", - "da-autocompliance", - "da-admin-service" - ); - } - - @Override - public void validateSubscriptionConfiguration(EventSubscriptionInputDTO inputDTO) { - if (inputDTO == null) { - throw new IllegalArgumentException("Subscription input cannot be null"); - } - - if (!StringUtils.hasText(inputDTO.getName())) { - throw new IllegalArgumentException("Subscription name is required"); - } - - if (inputDTO.getName().length() > 255) { - throw new IllegalArgumentException("Subscription name cannot exceed 255 characters"); - } - - if (inputDTO.getNotificationChannels() == null || inputDTO.getNotificationChannels().isEmpty()) { - throw new IllegalArgumentException("At least one notification channel is required"); - } - - // Validate rules if present - if (inputDTO.getRules() != null) { - for (EventSubscriptionInputDTO.RuleInputDTO rule : inputDTO.getRules()) { - if (StringUtils.hasText(rule.getCondition())) { - // Test the MVEL condition syntax - try { - new MVELRule() - .name("Validation Rule") - .description("Rule for validation") - .when(rule.getCondition()) - .then("true"); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid rule condition: " + e.getMessage()); - } - } - } - } - } - - // ==================== PRIVATE HELPER METHODS ==================== - - private EventSubscriptionOutputDTO convertToOutputDTO(EventSubscription subscription) { - EventSubscriptionOutputDTO outputDTO = new EventSubscriptionOutputDTO(); - outputDTO.setId(subscription.getId()); - outputDTO.setName(subscription.getName()); - outputDTO.setDescription(subscription.getDescription()); - outputDTO.setUserId(subscription.getUserId()); - outputDTO.setStatus(subscription.getStatus()); - outputDTO.setNotificationChannels(subscription.getNotificationChannels()); - outputDTO.setNotificationSettings(subscription.getNotificationSettings()); - outputDTO.setCreatedAt(subscription.getCreatedAt()); - outputDTO.setUpdatedAt(subscription.getUpdatedAt()); - outputDTO.setCreatedByUserId(subscription.getCreatedByUserId()); - outputDTO.setUpdatedByUserId(subscription.getUpdatedByUserId()); - - // Convert rules - if (subscription.getRules() != null) { - List ruleDTOs = subscription.getRules().stream() - .map(this::convertRuleToOutputDTO) - .collect(Collectors.toList()); - outputDTO.setRules(ruleDTOs); - } - - // Add mock statistics - EventSubscriptionOutputDTO.SubscriptionStatsDTO stats = new EventSubscriptionOutputDTO.SubscriptionStatsDTO(); - stats.setTotalEvents(125); - stats.setEventsToday(5); - stats.setLastEventAt(Instant.now().minusSeconds(3600)); - outputDTO.setStatistics(stats); - - return outputDTO; - } - - private EventSubscriptionOutputDTO.RuleOutputDTO convertRuleToOutputDTO(EventRule rule) { - EventSubscriptionOutputDTO.RuleOutputDTO ruleDTO = new EventSubscriptionOutputDTO.RuleOutputDTO(); - ruleDTO.setId(rule.getId()); - ruleDTO.setCondition(rule.getCondition()); - ruleDTO.setEventTypes(rule.getEventTypes()); - ruleDTO.setSeverityLevels(rule.getSeverityLevels()); - ruleDTO.setSourceServices(rule.getSourceServices()); - ruleDTO.setCreatedAt(rule.getCreatedAt()); - return ruleDTO; - } - - private boolean matchesSubscription(EventStreamDTO event, EventSubscription subscription) { - if (subscription.getRules() == null || subscription.getRules().isEmpty()) { - return true; // No rules means match all events - } - - for (EventRule rule : subscription.getRules()) { - try { - // Create MVEL rule for matching - Rule mvelRule = new MVELRule() - .name("Subscription Rule") - .description("Rule for subscription matching") - .when(rule.getCondition()) - .then("true"); - - Rules rules = new Rules(mvelRule); - Facts facts = new Facts(); - - // Add event data to facts - facts.put("event", event); - facts.put("eventType", event.getEventType()); - facts.put("severity", event.getSeverity()); - facts.put("sourceService", event.getSourceService()); - facts.put("assetId", event.getAssetId()); - facts.put("timestamp", event.getTimestamp()); - - // Execute rule - rulesEngine.fire(rules, facts); - - // If rule was triggered, event matches - if (facts.get("true") != null) { - return true; - } - - } catch (Exception e) { - log.error("Error matching event against rule {}: {}", rule.getId(), e.getMessage(), e); - } - } - - return false; - } - - private List generateMockEventsForSubscriptions(List subscriptions, int limit) { - List events = new ArrayList<>(); - - for (int i = 0; i < Math.min(limit, subscriptions.size() * 3); i++) { - EventStreamDTO event = new EventStreamDTO(); - event.setId(UUID.randomUUID()); - event.setEventType(EventType.ASSET_DISCOVERED); - event.setSeverity("INFO"); - event.setSourceService("da-discovery"); - event.setAssetId("asset-" + (i + 1)); - event.setTimestamp(Instant.now().minusSeconds(i * 60)); - event.setMessage("Mock event for testing"); - event.setMetadata(Map.of("test", "true", "index", String.valueOf(i))); - - events.add(event); - } - - return events; - } - - private List generateMockHistoricalEvents(UUID subscriptionId, int limit) { - List events = new ArrayList<>(); - - for (int i = 0; i < limit; i++) { - EventStreamDTO event = new EventStreamDTO(); - event.setId(UUID.randomUUID()); - event.setEventType(EventType.POLICY_VIOLATION); - event.setSeverity("WARNING"); - event.setSourceService("da-policyengine"); - event.setAssetId("asset-" + (i + 1)); - event.setTimestamp(Instant.now().minusSeconds(i * 3600)); // Historical events - event.setMessage("Historical mock event"); - event.setMetadata(Map.of("subscriptionId", subscriptionId.toString(), "historical", "true")); - - events.add(event); - } - - return events; - } +package com.dalab.policyengine.service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; +import org.jeasy.rules.api.Rules; +import org.jeasy.rules.api.RulesEngine; +import org.jeasy.rules.core.DefaultRulesEngine; +import org.jeasy.rules.mvel.MVELRule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import com.dalab.policyengine.common.ResourceNotFoundException; +import com.dalab.policyengine.dto.EventAnalyticsDTO; +import com.dalab.policyengine.dto.EventStreamDTO; +import com.dalab.policyengine.dto.EventSubscriptionInputDTO; +import com.dalab.policyengine.dto.EventSubscriptionOutputDTO; +import com.dalab.policyengine.model.EventRule; +import com.dalab.policyengine.model.EventSubscription; +import com.dalab.policyengine.model.EventSubscriptionStatus; +import com.dalab.policyengine.model.EventType; +import com.dalab.policyengine.repository.EventRuleRepository; +import com.dalab.policyengine.repository.EventSubscriptionRepository; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.persistence.criteria.Predicate; + +/** + * Service implementation for Event Subscription management and event streaming. + * Provides operations for creating, managing, and processing event subscriptions. + */ +@Service +@Transactional +public class EventSubscriptionService implements IEventSubscriptionService { + + private static final Logger log = LoggerFactory.getLogger(EventSubscriptionService.class); + + private final EventSubscriptionRepository eventSubscriptionRepository; + private final EventRuleRepository eventRuleRepository; + private final ObjectMapper objectMapper; + private final RulesEngine rulesEngine; + + @Autowired + public EventSubscriptionService(EventSubscriptionRepository eventSubscriptionRepository, + EventRuleRepository eventRuleRepository, + ObjectMapper objectMapper) { + this.eventSubscriptionRepository = eventSubscriptionRepository; + this.eventRuleRepository = eventRuleRepository; + this.objectMapper = objectMapper; + this.rulesEngine = new DefaultRulesEngine(); + } + + // ==================== SUBSCRIPTION MANAGEMENT ==================== + + @Override + public EventSubscriptionOutputDTO createSubscription(EventSubscriptionInputDTO inputDTO, UUID creatorUserId) { + log.info("Creating event subscription for user: {}", creatorUserId); + + // Validate input + validateSubscriptionConfiguration(inputDTO); + + // Create subscription entity + EventSubscription subscription = new EventSubscription(); + subscription.setName(inputDTO.getName()); + subscription.setDescription(inputDTO.getDescription()); + subscription.setUserId(creatorUserId); + subscription.setStatus(EventSubscriptionStatus.ACTIVE); + subscription.setNotificationChannels(inputDTO.getNotificationChannels()); + subscription.setNotificationSettings(inputDTO.getNotificationSettings()); + subscription.setCreatedAt(Instant.now()); + subscription.setUpdatedAt(Instant.now()); + subscription.setCreatedByUserId(creatorUserId); + subscription.setUpdatedByUserId(creatorUserId); + + // Save subscription + EventSubscription savedSubscription = eventSubscriptionRepository.save(subscription); + + // Create and save rules + if (inputDTO.getRules() != null && !inputDTO.getRules().isEmpty()) { + List rules = inputDTO.getRules().stream() + .map(ruleInput -> { + EventRule rule = new EventRule(); + rule.setSubscriptionId(savedSubscription.getId()); + rule.setCondition(ruleInput.getCondition()); + rule.setEventTypes(ruleInput.getEventTypes()); + rule.setSeverityLevels(ruleInput.getSeverityLevels()); + rule.setSourceServices(ruleInput.getSourceServices()); + rule.setCreatedAt(Instant.now()); + return eventRuleRepository.save(rule); + }) + .collect(Collectors.toList()); + + savedSubscription.setRules(rules); + } + + log.info("Successfully created event subscription: {}", savedSubscription.getId()); + return convertToOutputDTO(savedSubscription); + } + + @Override + public EventSubscriptionOutputDTO updateSubscription(UUID subscriptionId, EventSubscriptionInputDTO inputDTO, UUID updaterUserId) { + log.info("Updating event subscription: {}", subscriptionId); + + EventSubscription subscription = eventSubscriptionRepository.findById(subscriptionId) + .orElseThrow(() -> new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString())); + + // Validate input + validateSubscriptionConfiguration(inputDTO); + + // Update subscription fields + subscription.setName(inputDTO.getName()); + subscription.setDescription(inputDTO.getDescription()); + subscription.setNotificationChannels(inputDTO.getNotificationChannels()); + subscription.setNotificationSettings(inputDTO.getNotificationSettings()); + subscription.setUpdatedAt(Instant.now()); + subscription.setUpdatedByUserId(updaterUserId); + + // Update rules + if (inputDTO.getRules() != null) { + // Delete existing rules + eventRuleRepository.deleteBySubscriptionId(subscriptionId); + + // Create new rules + List rules = inputDTO.getRules().stream() + .map(ruleInput -> { + EventRule rule = new EventRule(); + rule.setSubscriptionId(subscriptionId); + rule.setCondition(ruleInput.getCondition()); + rule.setEventTypes(ruleInput.getEventTypes()); + rule.setSeverityLevels(ruleInput.getSeverityLevels()); + rule.setSourceServices(ruleInput.getSourceServices()); + rule.setCreatedAt(Instant.now()); + return eventRuleRepository.save(rule); + }) + .collect(Collectors.toList()); + + subscription.setRules(rules); + } + + EventSubscription updatedSubscription = eventSubscriptionRepository.save(subscription); + log.info("Successfully updated event subscription: {}", subscriptionId); + return convertToOutputDTO(updatedSubscription); + } + + @Override + @Transactional(readOnly = true) + public EventSubscriptionOutputDTO getSubscriptionById(UUID subscriptionId) { + EventSubscription subscription = eventSubscriptionRepository.findById(subscriptionId) + .orElseThrow(() -> new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString())); + return convertToOutputDTO(subscription); + } + + @Override + @Transactional(readOnly = true) + public Page getSubscriptionsForUser(UUID userId, Pageable pageable, String status, String nameContains) { + Specification spec = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + predicates.add(criteriaBuilder.equal(root.get("userId"), userId)); + + if (StringUtils.hasText(status)) { + try { + predicates.add(criteriaBuilder.equal(root.get("status"), EventSubscriptionStatus.valueOf(status.toUpperCase()))); + } catch (IllegalArgumentException e) { + log.warn("Invalid status provided for filtering: {}", status); + predicates.add(criteriaBuilder.disjunction()); + } + } + + if (StringUtils.hasText(nameContains)) { + predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), + "%" + nameContains.toLowerCase() + "%")); + } + + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + + Page subscriptionPage = eventSubscriptionRepository.findAll(spec, pageable); + return subscriptionPage.map(this::convertToOutputDTO); + } + + @Override + @Transactional(readOnly = true) + public Page getAllSubscriptions(Pageable pageable, String status, String nameContains) { + Specification spec = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + + if (StringUtils.hasText(status)) { + try { + predicates.add(criteriaBuilder.equal(root.get("status"), EventSubscriptionStatus.valueOf(status.toUpperCase()))); + } catch (IllegalArgumentException e) { + log.warn("Invalid status provided for filtering: {}", status); + predicates.add(criteriaBuilder.disjunction()); + } + } + + if (StringUtils.hasText(nameContains)) { + predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), + "%" + nameContains.toLowerCase() + "%")); + } + + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + + Page subscriptionPage = eventSubscriptionRepository.findAll(spec, pageable); + return subscriptionPage.map(this::convertToOutputDTO); + } + + @Override + public void deleteSubscription(UUID subscriptionId) { + if (!eventSubscriptionRepository.existsById(subscriptionId)) { + throw new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString()); + } + + // Delete associated rules first + eventRuleRepository.deleteBySubscriptionId(subscriptionId); + + // Delete subscription + eventSubscriptionRepository.deleteById(subscriptionId); + log.info("Successfully deleted event subscription: {}", subscriptionId); + } + + @Override + public EventSubscriptionOutputDTO updateSubscriptionStatus(UUID subscriptionId, EventSubscriptionStatus status, UUID updaterUserId) { + EventSubscription subscription = eventSubscriptionRepository.findById(subscriptionId) + .orElseThrow(() -> new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString())); + + subscription.setStatus(status); + subscription.setUpdatedAt(Instant.now()); + subscription.setUpdatedByUserId(updaterUserId); + + EventSubscription updatedSubscription = eventSubscriptionRepository.save(subscription); + log.info("Updated subscription {} status to: {}", subscriptionId, status); + return convertToOutputDTO(updatedSubscription); + } + + // ==================== EVENT STREAMING AND PROCESSING ==================== + + @Override + @Transactional(readOnly = true) + public List getEventStreamForUser(UUID userId, Integer limit) { + log.info("Getting event stream for user: {} with limit: {}", userId, limit); + + // Get user's active subscriptions + List activeSubscriptions = eventSubscriptionRepository + .findByUserIdAndStatus(userId, EventSubscriptionStatus.ACTIVE); + + if (activeSubscriptions.isEmpty()) { + return new ArrayList<>(); + } + + // For now, return mock events that would match user's subscriptions + // In a real implementation, this would query actual event streams + return generateMockEventsForSubscriptions(activeSubscriptions, limit != null ? limit : 50); + } + + @Override + @Transactional(readOnly = true) + public List getAllEventStream(Integer limit) { + log.info("Getting all event stream with limit: {}", limit); + + // Return mock events for all subscriptions + List allSubscriptions = eventSubscriptionRepository.findAll(); + return generateMockEventsForSubscriptions(allSubscriptions, limit != null ? limit : 100); + } + + @Override + public void processIncomingEvent(EventStreamDTO eventDTO) { + log.info("Processing incoming event: {} from service: {}", eventDTO.getEventType(), eventDTO.getSourceService()); + + // Find all active subscriptions that might match this event + List activeSubscriptions = eventSubscriptionRepository + .findByStatus(EventSubscriptionStatus.ACTIVE); + + for (EventSubscription subscription : activeSubscriptions) { + try { + if (matchesSubscription(eventDTO, subscription)) { + log.info("Event matches subscription: {}", subscription.getId()); + // In a real implementation, this would trigger notifications + // For now, just log the match + } + } catch (Exception e) { + log.error("Error processing event for subscription {}: {}", subscription.getId(), e.getMessage(), e); + } + } + } + + @Override + @Transactional(readOnly = true) + public Page getHistoricalEventsForSubscription(UUID subscriptionId, Pageable pageable) { + // Verify subscription exists + if (!eventSubscriptionRepository.existsById(subscriptionId)) { + throw new ResourceNotFoundException("EventSubscription", "id", subscriptionId.toString()); + } + + // For now, return mock historical events + // In a real implementation, this would query actual event history + List mockEvents = generateMockHistoricalEvents(subscriptionId, pageable.getPageSize()); + + // Create a simple page implementation + return new org.springframework.data.domain.PageImpl<>( + mockEvents, + pageable, + mockEvents.size() + ); + } + + // ==================== ANALYTICS AND METRICS ==================== + + @Override + @Transactional(readOnly = true) + public EventAnalyticsDTO getEventAnalyticsForUser(UUID userId) { + log.info("Getting event analytics for user: {}", userId); + + List userSubscriptions = eventSubscriptionRepository.findByUserId(userId); + + EventAnalyticsDTO analytics = new EventAnalyticsDTO(); + analytics.setUserId(userId); + analytics.setTotalSubscriptions(userSubscriptions.size()); + analytics.setActiveSubscriptions((int) userSubscriptions.stream() + .filter(s -> s.getStatus() == EventSubscriptionStatus.ACTIVE) + .count()); + + // Add mock metrics + EventAnalyticsDTO.EventMetricsDTO metrics = new EventAnalyticsDTO.EventMetricsDTO(); + metrics.setTotalEvents(1250); + metrics.setEventsToday(45); + metrics.setEventsThisWeek(320); + metrics.setEventsThisMonth(1250); + analytics.setEventMetrics(metrics); + + // Add mock trends + EventAnalyticsDTO.EventTrendsDTO trends = new EventAnalyticsDTO.EventTrendsDTO(); + trends.setTrendDirection("increasing"); + trends.setTrendPercentage(12.5); + trends.setPeakHour("14:00"); + trends.setPeakDay("Wednesday"); + analytics.setEventTrends(trends); + + return analytics; + } + + @Override + @Transactional(readOnly = true) + public EventAnalyticsDTO getSystemEventAnalytics() { + log.info("Getting system-wide event analytics"); + + List allSubscriptions = eventSubscriptionRepository.findAll(); + + EventAnalyticsDTO analytics = new EventAnalyticsDTO(); + analytics.setTotalSubscriptions(allSubscriptions.size()); + analytics.setActiveSubscriptions((int) allSubscriptions.stream() + .filter(s -> s.getStatus() == EventSubscriptionStatus.ACTIVE) + .count()); + + // Add comprehensive system metrics + EventAnalyticsDTO.EventMetricsDTO metrics = new EventAnalyticsDTO.EventMetricsDTO(); + metrics.setTotalEvents(8750); + metrics.setEventsToday(245); + metrics.setEventsThisWeek(1820); + metrics.setEventsThisMonth(8750); + analytics.setEventMetrics(metrics); + + // Add system trends + EventAnalyticsDTO.EventTrendsDTO trends = new EventAnalyticsDTO.EventTrendsDTO(); + trends.setTrendDirection("stable"); + trends.setTrendPercentage(2.1); + trends.setPeakHour("10:00"); + trends.setPeakDay("Tuesday"); + analytics.setEventTrends(trends); + + return analytics; + } + + @Override + @Transactional(readOnly = true) + public EventAnalyticsDTO getEventAnalyticsForTimeRange(UUID userId, Instant fromTime, Instant toTime) { + log.info("Getting event analytics for user: {} from {} to {}", userId, fromTime, toTime); + + // For now, return mock analytics for the time range + // In a real implementation, this would query actual event data + EventAnalyticsDTO analytics = new EventAnalyticsDTO(); + analytics.setUserId(userId); + analytics.setTotalSubscriptions(5); + analytics.setActiveSubscriptions(3); + + EventAnalyticsDTO.EventMetricsDTO metrics = new EventAnalyticsDTO.EventMetricsDTO(); + metrics.setTotalEvents(450); + metrics.setEventsToday(25); + metrics.setEventsThisWeek(180); + metrics.setEventsThisMonth(450); + analytics.setEventMetrics(metrics); + + return analytics; + } + + // ==================== UTILITY METHODS ==================== + + @Override + public boolean testEventRule(String ruleCondition, EventStreamDTO sampleEvent) { + try { + // Create MVEL rule for testing + Rule testRule = new MVELRule() + .name("Test Rule") + .description("Test rule for validation") + .when(ruleCondition) + .then("true"); + + Rules rules = new Rules(testRule); + Facts facts = new Facts(); + + // Add event data to facts + facts.put("event", sampleEvent); + facts.put("eventType", sampleEvent.getEventType()); + facts.put("severity", sampleEvent.getSeverity()); + facts.put("sourceService", sampleEvent.getSourceService()); + facts.put("assetId", sampleEvent.getAssetId()); + facts.put("timestamp", sampleEvent.getTimestamp()); + + // Execute rule + rulesEngine.fire(rules, facts); + + // Check if rule was triggered + return facts.get("true") != null; + + } catch (Exception e) { + log.error("Error testing event rule: {}", e.getMessage(), e); + return false; + } + } + + @Override + public List getAvailableEventTypes() { + return Arrays.asList(EventType.values()); + } + + @Override + public List getAvailableSourceServices() { + return Arrays.asList( + "da-discovery", + "da-catalog", + "da-policyengine", + "da-reporting", + "da-autolabel", + "da-autoarchival", + "da-autodelete", + "da-autocompliance", + "da-admin-service" + ); + } + + @Override + public void validateSubscriptionConfiguration(EventSubscriptionInputDTO inputDTO) { + if (inputDTO == null) { + throw new IllegalArgumentException("Subscription input cannot be null"); + } + + if (!StringUtils.hasText(inputDTO.getName())) { + throw new IllegalArgumentException("Subscription name is required"); + } + + if (inputDTO.getName().length() > 255) { + throw new IllegalArgumentException("Subscription name cannot exceed 255 characters"); + } + + if (inputDTO.getNotificationChannels() == null || inputDTO.getNotificationChannels().isEmpty()) { + throw new IllegalArgumentException("At least one notification channel is required"); + } + + // Validate rules if present + if (inputDTO.getRules() != null) { + for (EventSubscriptionInputDTO.RuleInputDTO rule : inputDTO.getRules()) { + if (StringUtils.hasText(rule.getCondition())) { + // Test the MVEL condition syntax + try { + new MVELRule() + .name("Validation Rule") + .description("Rule for validation") + .when(rule.getCondition()) + .then("true"); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid rule condition: " + e.getMessage()); + } + } + } + } + } + + // ==================== PRIVATE HELPER METHODS ==================== + + private EventSubscriptionOutputDTO convertToOutputDTO(EventSubscription subscription) { + EventSubscriptionOutputDTO outputDTO = new EventSubscriptionOutputDTO(); + outputDTO.setId(subscription.getId()); + outputDTO.setName(subscription.getName()); + outputDTO.setDescription(subscription.getDescription()); + outputDTO.setUserId(subscription.getUserId()); + outputDTO.setStatus(subscription.getStatus()); + outputDTO.setNotificationChannels(subscription.getNotificationChannels()); + outputDTO.setNotificationSettings(subscription.getNotificationSettings()); + outputDTO.setCreatedAt(subscription.getCreatedAt()); + outputDTO.setUpdatedAt(subscription.getUpdatedAt()); + outputDTO.setCreatedByUserId(subscription.getCreatedByUserId()); + outputDTO.setUpdatedByUserId(subscription.getUpdatedByUserId()); + + // Convert rules + if (subscription.getRules() != null) { + List ruleDTOs = subscription.getRules().stream() + .map(this::convertRuleToOutputDTO) + .collect(Collectors.toList()); + outputDTO.setRules(ruleDTOs); + } + + // Add mock statistics + EventSubscriptionOutputDTO.SubscriptionStatsDTO stats = new EventSubscriptionOutputDTO.SubscriptionStatsDTO(); + stats.setTotalEvents(125); + stats.setEventsToday(5); + stats.setLastEventAt(Instant.now().minusSeconds(3600)); + outputDTO.setStatistics(stats); + + return outputDTO; + } + + private EventSubscriptionOutputDTO.RuleOutputDTO convertRuleToOutputDTO(EventRule rule) { + EventSubscriptionOutputDTO.RuleOutputDTO ruleDTO = new EventSubscriptionOutputDTO.RuleOutputDTO(); + ruleDTO.setId(rule.getId()); + ruleDTO.setCondition(rule.getCondition()); + ruleDTO.setEventTypes(rule.getEventTypes()); + ruleDTO.setSeverityLevels(rule.getSeverityLevels()); + ruleDTO.setSourceServices(rule.getSourceServices()); + ruleDTO.setCreatedAt(rule.getCreatedAt()); + return ruleDTO; + } + + private boolean matchesSubscription(EventStreamDTO event, EventSubscription subscription) { + if (subscription.getRules() == null || subscription.getRules().isEmpty()) { + return true; // No rules means match all events + } + + for (EventRule rule : subscription.getRules()) { + try { + // Create MVEL rule for matching + Rule mvelRule = new MVELRule() + .name("Subscription Rule") + .description("Rule for subscription matching") + .when(rule.getCondition()) + .then("true"); + + Rules rules = new Rules(mvelRule); + Facts facts = new Facts(); + + // Add event data to facts + facts.put("event", event); + facts.put("eventType", event.getEventType()); + facts.put("severity", event.getSeverity()); + facts.put("sourceService", event.getSourceService()); + facts.put("assetId", event.getAssetId()); + facts.put("timestamp", event.getTimestamp()); + + // Execute rule + rulesEngine.fire(rules, facts); + + // If rule was triggered, event matches + if (facts.get("true") != null) { + return true; + } + + } catch (Exception e) { + log.error("Error matching event against rule {}: {}", rule.getId(), e.getMessage(), e); + } + } + + return false; + } + + private List generateMockEventsForSubscriptions(List subscriptions, int limit) { + List events = new ArrayList<>(); + + for (int i = 0; i < Math.min(limit, subscriptions.size() * 3); i++) { + EventStreamDTO event = new EventStreamDTO(); + event.setId(UUID.randomUUID()); + event.setEventType(EventType.ASSET_DISCOVERED); + event.setSeverity("INFO"); + event.setSourceService("da-discovery"); + event.setAssetId("asset-" + (i + 1)); + event.setTimestamp(Instant.now().minusSeconds(i * 60)); + event.setMessage("Mock event for testing"); + event.setMetadata(Map.of("test", "true", "index", String.valueOf(i))); + + events.add(event); + } + + return events; + } + + private List generateMockHistoricalEvents(UUID subscriptionId, int limit) { + List events = new ArrayList<>(); + + for (int i = 0; i < limit; i++) { + EventStreamDTO event = new EventStreamDTO(); + event.setId(UUID.randomUUID()); + event.setEventType(EventType.POLICY_VIOLATION); + event.setSeverity("WARNING"); + event.setSourceService("da-policyengine"); + event.setAssetId("asset-" + (i + 1)); + event.setTimestamp(Instant.now().minusSeconds(i * 3600)); // Historical events + event.setMessage("Historical mock event"); + event.setMetadata(Map.of("subscriptionId", subscriptionId.toString(), "historical", "true")); + + events.add(event); + } + + return events; + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/service/IEventSubscriptionService.java b/src/main/java/com/dalab/policyengine/service/IEventSubscriptionService.java index 65f28d3423c0c44f62ba749726aaa3efcecfd8e5..cc40b7b0bd1f9b7caf5ea25f7219488af57bd837 100644 --- a/src/main/java/com/dalab/policyengine/service/IEventSubscriptionService.java +++ b/src/main/java/com/dalab/policyengine/service/IEventSubscriptionService.java @@ -1,120 +1,120 @@ -package com.dalab.policyengine.service; - -import java.util.List; -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import com.dalab.policyengine.dto.EventAnalyticsDTO; -import com.dalab.policyengine.dto.EventStreamDTO; -import com.dalab.policyengine.dto.EventSubscriptionInputDTO; -import com.dalab.policyengine.dto.EventSubscriptionOutputDTO; -import com.dalab.policyengine.model.EventSubscriptionStatus; -import com.dalab.policyengine.model.EventType; - -/** - * Service interface for Event Subscription management and event streaming. - * Provides operations for creating, managing, and processing event subscriptions. - */ -public interface IEventSubscriptionService { - - // Subscription Management - - /** - * Create a new event subscription for the authenticated user - */ - EventSubscriptionOutputDTO createSubscription(EventSubscriptionInputDTO inputDTO, UUID creatorUserId); - - /** - * Update an existing event subscription - */ - EventSubscriptionOutputDTO updateSubscription(UUID subscriptionId, EventSubscriptionInputDTO inputDTO, UUID updaterUserId); - - /** - * Get event subscription by ID - */ - EventSubscriptionOutputDTO getSubscriptionById(UUID subscriptionId); - - /** - * Get all event subscriptions for a user with pagination - */ - Page getSubscriptionsForUser(UUID userId, Pageable pageable, String status, String nameContains); - - /** - * Get all event subscriptions (admin only) with pagination - */ - Page getAllSubscriptions(Pageable pageable, String status, String nameContains); - - /** - * Delete an event subscription - */ - void deleteSubscription(UUID subscriptionId); - - /** - * Update subscription status (enable/disable/pause) - */ - EventSubscriptionOutputDTO updateSubscriptionStatus(UUID subscriptionId, EventSubscriptionStatus status, UUID updaterUserId); - - // Event Streaming and Processing - - /** - * Get real-time event stream for a user's subscriptions - * This would typically be used with WebSocket or Server-Sent Events - */ - List getEventStreamForUser(UUID userId, Integer limit); - - /** - * Get event stream for all subscriptions (admin only) - */ - List getAllEventStream(Integer limit); - - /** - * Process an incoming event and match it against active subscriptions - */ - void processIncomingEvent(EventStreamDTO eventDTO); - - /** - * Get historical events for a subscription - */ - Page getHistoricalEventsForSubscription(UUID subscriptionId, Pageable pageable); - - // Analytics and Metrics - - /** - * Get event analytics for a user's subscriptions - */ - EventAnalyticsDTO getEventAnalyticsForUser(UUID userId); - - /** - * Get system-wide event analytics (admin only) - */ - EventAnalyticsDTO getSystemEventAnalytics(); - - /** - * Get event analytics for a specific time range - */ - EventAnalyticsDTO getEventAnalyticsForTimeRange(UUID userId, java.time.Instant fromTime, java.time.Instant toTime); - - // Utility Methods - - /** - * Test event rules against sample data - */ - boolean testEventRule(String ruleCondition, EventStreamDTO sampleEvent); - - /** - * Get available event types for subscription configuration - */ - List getAvailableEventTypes(); - - /** - * Get available source services for subscription configuration - */ - List getAvailableSourceServices(); - - /** - * Validate subscription configuration - */ - void validateSubscriptionConfiguration(EventSubscriptionInputDTO inputDTO); +package com.dalab.policyengine.service; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import com.dalab.policyengine.dto.EventAnalyticsDTO; +import com.dalab.policyengine.dto.EventStreamDTO; +import com.dalab.policyengine.dto.EventSubscriptionInputDTO; +import com.dalab.policyengine.dto.EventSubscriptionOutputDTO; +import com.dalab.policyengine.model.EventSubscriptionStatus; +import com.dalab.policyengine.model.EventType; + +/** + * Service interface for Event Subscription management and event streaming. + * Provides operations for creating, managing, and processing event subscriptions. + */ +public interface IEventSubscriptionService { + + // Subscription Management + + /** + * Create a new event subscription for the authenticated user + */ + EventSubscriptionOutputDTO createSubscription(EventSubscriptionInputDTO inputDTO, UUID creatorUserId); + + /** + * Update an existing event subscription + */ + EventSubscriptionOutputDTO updateSubscription(UUID subscriptionId, EventSubscriptionInputDTO inputDTO, UUID updaterUserId); + + /** + * Get event subscription by ID + */ + EventSubscriptionOutputDTO getSubscriptionById(UUID subscriptionId); + + /** + * Get all event subscriptions for a user with pagination + */ + Page getSubscriptionsForUser(UUID userId, Pageable pageable, String status, String nameContains); + + /** + * Get all event subscriptions (admin only) with pagination + */ + Page getAllSubscriptions(Pageable pageable, String status, String nameContains); + + /** + * Delete an event subscription + */ + void deleteSubscription(UUID subscriptionId); + + /** + * Update subscription status (enable/disable/pause) + */ + EventSubscriptionOutputDTO updateSubscriptionStatus(UUID subscriptionId, EventSubscriptionStatus status, UUID updaterUserId); + + // Event Streaming and Processing + + /** + * Get real-time event stream for a user's subscriptions + * This would typically be used with WebSocket or Server-Sent Events + */ + List getEventStreamForUser(UUID userId, Integer limit); + + /** + * Get event stream for all subscriptions (admin only) + */ + List getAllEventStream(Integer limit); + + /** + * Process an incoming event and match it against active subscriptions + */ + void processIncomingEvent(EventStreamDTO eventDTO); + + /** + * Get historical events for a subscription + */ + Page getHistoricalEventsForSubscription(UUID subscriptionId, Pageable pageable); + + // Analytics and Metrics + + /** + * Get event analytics for a user's subscriptions + */ + EventAnalyticsDTO getEventAnalyticsForUser(UUID userId); + + /** + * Get system-wide event analytics (admin only) + */ + EventAnalyticsDTO getSystemEventAnalytics(); + + /** + * Get event analytics for a specific time range + */ + EventAnalyticsDTO getEventAnalyticsForTimeRange(UUID userId, java.time.Instant fromTime, java.time.Instant toTime); + + // Utility Methods + + /** + * Test event rules against sample data + */ + boolean testEventRule(String ruleCondition, EventStreamDTO sampleEvent); + + /** + * Get available event types for subscription configuration + */ + List getAvailableEventTypes(); + + /** + * Get available source services for subscription configuration + */ + List getAvailableSourceServices(); + + /** + * Validate subscription configuration + */ + void validateSubscriptionConfiguration(EventSubscriptionInputDTO inputDTO); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/service/IPolicyEvaluationService.java b/src/main/java/com/dalab/policyengine/service/IPolicyEvaluationService.java index 3a8a4189db5d5cdb141610cb54a66d07ad0495f5..f24445c2c84af7533fe3038778bbf2c4581a4f04 100644 --- a/src/main/java/com/dalab/policyengine/service/IPolicyEvaluationService.java +++ b/src/main/java/com/dalab/policyengine/service/IPolicyEvaluationService.java @@ -1,26 +1,26 @@ -package com.dalab.policyengine.service; - -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import com.dalab.common.event.AssetChangeEvent; -import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; -import com.dalab.policyengine.dto.PolicyEvaluationRequestDTO; -import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; - -public interface IPolicyEvaluationService { - - PolicyEvaluationOutputDTO evaluatePolicyForAsset(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId); - - // Method expected by controller - delegates to evaluatePolicyForAsset - PolicyEvaluationOutputDTO triggerPolicyEvaluation(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId); - - Page getPolicyEvaluations(Pageable pageable, UUID policyId, String targetAssetId, String status); - - PolicyEvaluationOutputDTO getPolicyEvaluationById(UUID evaluationId); - - // Internal method for Kafka consumer or scheduled tasks - void evaluatePolicyForAssetInternal(AssetChangeEvent assetChangeEvent, UUID eventInitiatorId); +package com.dalab.policyengine.service; + +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import com.dalab.common.event.AssetChangeEvent; +import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; +import com.dalab.policyengine.dto.PolicyEvaluationRequestDTO; +import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; + +public interface IPolicyEvaluationService { + + PolicyEvaluationOutputDTO evaluatePolicyForAsset(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId); + + // Method expected by controller - delegates to evaluatePolicyForAsset + PolicyEvaluationOutputDTO triggerPolicyEvaluation(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId); + + Page getPolicyEvaluations(Pageable pageable, UUID policyId, String targetAssetId, String status); + + PolicyEvaluationOutputDTO getPolicyEvaluationById(UUID evaluationId); + + // Internal method for Kafka consumer or scheduled tasks + void evaluatePolicyForAssetInternal(AssetChangeEvent assetChangeEvent, UUID eventInitiatorId); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/service/IPolicyService.java b/src/main/java/com/dalab/policyengine/service/IPolicyService.java index fc6b9d68c575e854acd1b77948dca088afa06492..51c7b0b390ee137ac47c14c78c844aba8ce56689 100644 --- a/src/main/java/com/dalab/policyengine/service/IPolicyService.java +++ b/src/main/java/com/dalab/policyengine/service/IPolicyService.java @@ -1,225 +1,225 @@ -package com.dalab.policyengine.service; - -import java.util.List; -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import com.dalab.policyengine.dto.PolicyDraftActionDTO; -import com.dalab.policyengine.dto.PolicyDraftInputDTO; -import com.dalab.policyengine.dto.PolicyDraftOutputDTO; -import com.dalab.policyengine.dto.PolicyImpactRequestDTO; -import com.dalab.policyengine.dto.PolicyImpactResponseDTO; -import com.dalab.policyengine.dto.PolicyInputDTO; -import com.dalab.policyengine.dto.PolicyOutputDTO; -import com.dalab.policyengine.dto.PolicySummaryDTO; -import com.dalab.policyengine.model.PolicyDraftStatus; - -public interface IPolicyService { - Page getAllPolicies(Pageable pageable, String status, String nameContains); - PolicyOutputDTO getPolicyById(UUID policyId); - PolicyOutputDTO createPolicy(PolicyInputDTO policyInputDTO, UUID creatorUserId); - PolicyOutputDTO updatePolicy(UUID policyId, PolicyInputDTO policyInputDTO, UUID updaterUserId); - void deletePolicy(UUID policyId); - - /** - * Analyze the potential impact of a policy before implementation. - * Provides comprehensive assessment of affected assets, performance impact, - * cost implications, and compliance analysis. - * - * @param request the policy impact analysis request containing rules and analysis parameters - * @return comprehensive impact analysis response with affected assets and risk assessment - */ - PolicyImpactResponseDTO analyzePolicy(PolicyImpactRequestDTO request); - - /** - * Create a new policy draft. - * - * @param draftInput the draft input data - * @param creatorUserId the ID of the user creating the draft - * @return the created draft output DTO - */ - PolicyDraftOutputDTO createDraft(PolicyDraftInputDTO draftInput, UUID creatorUserId); - - /** - * Update an existing policy draft (only allowed in CREATED or REQUIRES_CHANGES status). - * - * @param draftId the ID of the draft to update - * @param draftInput the updated draft data - * @param updaterUserId the ID of the user updating the draft - * @return the updated draft output DTO - */ - PolicyDraftOutputDTO updateDraft(UUID draftId, PolicyDraftInputDTO draftInput, UUID updaterUserId); - - /** - * Get a policy draft by ID with full details including workflow information. - * - * @param draftId the ID of the draft - * @return the draft output DTO with complete information - */ - PolicyDraftOutputDTO getDraftById(UUID draftId); - - /** - * Get all policy drafts with filtering and pagination. - * - * @param pageable pagination information - * @param status optional status filter - * @param category optional category filter - * @param priority optional priority filter - * @param createdBy optional creator filter - * @param nameContains optional name search filter - * @return paginated list of draft summaries - */ - Page getAllDrafts( - Pageable pageable, - PolicyDraftStatus status, - String category, - String priority, - UUID createdBy, - String nameContains - ); - - /** - * Get drafts requiring attention from a specific user (created by them or they are stakeholder). - * - * @param userId the user ID - * @param pageable pagination information - * @return paginated list of drafts requiring attention - */ - Page getDraftsRequiringAttention(UUID userId, Pageable pageable); - - /** - * Get drafts pending review (submitted or under review status). - * - * @param pageable pagination information - * @return paginated list of drafts pending review - */ - Page getDraftsPendingReview(Pageable pageable); - - /** - * Submit a draft for review (transition from CREATED to SUBMITTED). - * - * @param draftId the ID of the draft to submit - * @param action the submit action DTO with optional comment - * @param submittedByUserId the ID of the user submitting the draft - * @return the updated draft output DTO - */ - PolicyDraftOutputDTO submitDraft(UUID draftId, PolicyDraftActionDTO action, UUID submittedByUserId); - - /** - * Approve a draft (transition to APPROVED status). - * - * @param draftId the ID of the draft to approve - * @param action the approval action DTO with optional comment - * @param approverUserId the ID of the user approving the draft - * @return the updated draft output DTO - */ - PolicyDraftOutputDTO approveDraft(UUID draftId, PolicyDraftActionDTO action, UUID approverUserId); - - /** - * Reject a draft (transition to REJECTED status). - * - * @param draftId the ID of the draft to reject - * @param action the rejection action DTO with required comment - * @param rejectorUserId the ID of the user rejecting the draft - * @return the updated draft output DTO - */ - PolicyDraftOutputDTO rejectDraft(UUID draftId, PolicyDraftActionDTO action, UUID rejectorUserId); - - /** - * Request changes to a draft (transition to REQUIRES_CHANGES status). - * - * @param draftId the ID of the draft requiring changes - * @param action the action DTO with required comment explaining needed changes - * @param reviewerUserId the ID of the user requesting changes - * @return the updated draft output DTO - */ - PolicyDraftOutputDTO requestChanges(UUID draftId, PolicyDraftActionDTO action, UUID reviewerUserId); - - /** - * Publish an approved draft as an active policy. - * - * @param draftId the ID of the draft to publish - * @param action the publish action DTO with optional metadata - * @param publisherUserId the ID of the user publishing the draft - * @return the published policy output DTO - */ - PolicyOutputDTO publishDraft(UUID draftId, PolicyDraftActionDTO action, UUID publisherUserId); - - /** - * Archive a draft (transition to ARCHIVED status). - * - * @param draftId the ID of the draft to archive - * @param action the archive action DTO with optional comment - * @param archiverUserId the ID of the user archiving the draft - * @return the updated draft output DTO - */ - PolicyDraftOutputDTO archiveDraft(UUID draftId, PolicyDraftActionDTO action, UUID archiverUserId); - - /** - * Add a review comment to a draft. - * - * @param draftId the ID of the draft - * @param comment the review comment - * @param reviewerUserId the ID of the reviewer - * @param reviewerRole the role of the reviewer - * @return the updated draft output DTO - */ - PolicyDraftOutputDTO addReviewComment(UUID draftId, String comment, UUID reviewerUserId, String reviewerRole); - - /** - * Delete a draft (only allowed in CREATED status). - * - * @param draftId the ID of the draft to delete - * @param deleterUserId the ID of the user deleting the draft - */ - void deleteDraft(UUID draftId, UUID deleterUserId); - - /** - * Get available categories for drafts. - * - * @return list of available categories - */ - List getDraftCategories(); - - /** - * Get available tags used in drafts. - * - * @return list of available tags - */ - List getDraftTags(); - - /** - * Get draft statistics for dashboard displays. - * - * @return draft statistics grouped by status - */ - List getDraftStatistics(); - - /** - * Get overdue drafts (target implementation date passed and not published). - * - * @return list of overdue drafts - */ - List getOverdueDrafts(); - - /** - * Clone an existing policy as a new draft for modification. - * - * @param policyId the ID of the policy to clone - * @param creatorUserId the ID of the user creating the draft - * @return the created draft output DTO - */ - PolicyDraftOutputDTO clonePolicyAsDraft(UUID policyId, UUID creatorUserId); - - /** - * Create a new version of an existing draft. - * - * @param draftId the ID of the draft to create new version from - * @param draftInput the updated draft data - * @param creatorUserId the ID of the user creating the new version - * @return the new version draft output DTO - */ - PolicyDraftOutputDTO createDraftVersion(UUID draftId, PolicyDraftInputDTO draftInput, UUID creatorUserId); +package com.dalab.policyengine.service; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import com.dalab.policyengine.dto.PolicyDraftActionDTO; +import com.dalab.policyengine.dto.PolicyDraftInputDTO; +import com.dalab.policyengine.dto.PolicyDraftOutputDTO; +import com.dalab.policyengine.dto.PolicyImpactRequestDTO; +import com.dalab.policyengine.dto.PolicyImpactResponseDTO; +import com.dalab.policyengine.dto.PolicyInputDTO; +import com.dalab.policyengine.dto.PolicyOutputDTO; +import com.dalab.policyengine.dto.PolicySummaryDTO; +import com.dalab.policyengine.model.PolicyDraftStatus; + +public interface IPolicyService { + Page getAllPolicies(Pageable pageable, String status, String nameContains); + PolicyOutputDTO getPolicyById(UUID policyId); + PolicyOutputDTO createPolicy(PolicyInputDTO policyInputDTO, UUID creatorUserId); + PolicyOutputDTO updatePolicy(UUID policyId, PolicyInputDTO policyInputDTO, UUID updaterUserId); + void deletePolicy(UUID policyId); + + /** + * Analyze the potential impact of a policy before implementation. + * Provides comprehensive assessment of affected assets, performance impact, + * cost implications, and compliance analysis. + * + * @param request the policy impact analysis request containing rules and analysis parameters + * @return comprehensive impact analysis response with affected assets and risk assessment + */ + PolicyImpactResponseDTO analyzePolicy(PolicyImpactRequestDTO request); + + /** + * Create a new policy draft. + * + * @param draftInput the draft input data + * @param creatorUserId the ID of the user creating the draft + * @return the created draft output DTO + */ + PolicyDraftOutputDTO createDraft(PolicyDraftInputDTO draftInput, UUID creatorUserId); + + /** + * Update an existing policy draft (only allowed in CREATED or REQUIRES_CHANGES status). + * + * @param draftId the ID of the draft to update + * @param draftInput the updated draft data + * @param updaterUserId the ID of the user updating the draft + * @return the updated draft output DTO + */ + PolicyDraftOutputDTO updateDraft(UUID draftId, PolicyDraftInputDTO draftInput, UUID updaterUserId); + + /** + * Get a policy draft by ID with full details including workflow information. + * + * @param draftId the ID of the draft + * @return the draft output DTO with complete information + */ + PolicyDraftOutputDTO getDraftById(UUID draftId); + + /** + * Get all policy drafts with filtering and pagination. + * + * @param pageable pagination information + * @param status optional status filter + * @param category optional category filter + * @param priority optional priority filter + * @param createdBy optional creator filter + * @param nameContains optional name search filter + * @return paginated list of draft summaries + */ + Page getAllDrafts( + Pageable pageable, + PolicyDraftStatus status, + String category, + String priority, + UUID createdBy, + String nameContains + ); + + /** + * Get drafts requiring attention from a specific user (created by them or they are stakeholder). + * + * @param userId the user ID + * @param pageable pagination information + * @return paginated list of drafts requiring attention + */ + Page getDraftsRequiringAttention(UUID userId, Pageable pageable); + + /** + * Get drafts pending review (submitted or under review status). + * + * @param pageable pagination information + * @return paginated list of drafts pending review + */ + Page getDraftsPendingReview(Pageable pageable); + + /** + * Submit a draft for review (transition from CREATED to SUBMITTED). + * + * @param draftId the ID of the draft to submit + * @param action the submit action DTO with optional comment + * @param submittedByUserId the ID of the user submitting the draft + * @return the updated draft output DTO + */ + PolicyDraftOutputDTO submitDraft(UUID draftId, PolicyDraftActionDTO action, UUID submittedByUserId); + + /** + * Approve a draft (transition to APPROVED status). + * + * @param draftId the ID of the draft to approve + * @param action the approval action DTO with optional comment + * @param approverUserId the ID of the user approving the draft + * @return the updated draft output DTO + */ + PolicyDraftOutputDTO approveDraft(UUID draftId, PolicyDraftActionDTO action, UUID approverUserId); + + /** + * Reject a draft (transition to REJECTED status). + * + * @param draftId the ID of the draft to reject + * @param action the rejection action DTO with required comment + * @param rejectorUserId the ID of the user rejecting the draft + * @return the updated draft output DTO + */ + PolicyDraftOutputDTO rejectDraft(UUID draftId, PolicyDraftActionDTO action, UUID rejectorUserId); + + /** + * Request changes to a draft (transition to REQUIRES_CHANGES status). + * + * @param draftId the ID of the draft requiring changes + * @param action the action DTO with required comment explaining needed changes + * @param reviewerUserId the ID of the user requesting changes + * @return the updated draft output DTO + */ + PolicyDraftOutputDTO requestChanges(UUID draftId, PolicyDraftActionDTO action, UUID reviewerUserId); + + /** + * Publish an approved draft as an active policy. + * + * @param draftId the ID of the draft to publish + * @param action the publish action DTO with optional metadata + * @param publisherUserId the ID of the user publishing the draft + * @return the published policy output DTO + */ + PolicyOutputDTO publishDraft(UUID draftId, PolicyDraftActionDTO action, UUID publisherUserId); + + /** + * Archive a draft (transition to ARCHIVED status). + * + * @param draftId the ID of the draft to archive + * @param action the archive action DTO with optional comment + * @param archiverUserId the ID of the user archiving the draft + * @return the updated draft output DTO + */ + PolicyDraftOutputDTO archiveDraft(UUID draftId, PolicyDraftActionDTO action, UUID archiverUserId); + + /** + * Add a review comment to a draft. + * + * @param draftId the ID of the draft + * @param comment the review comment + * @param reviewerUserId the ID of the reviewer + * @param reviewerRole the role of the reviewer + * @return the updated draft output DTO + */ + PolicyDraftOutputDTO addReviewComment(UUID draftId, String comment, UUID reviewerUserId, String reviewerRole); + + /** + * Delete a draft (only allowed in CREATED status). + * + * @param draftId the ID of the draft to delete + * @param deleterUserId the ID of the user deleting the draft + */ + void deleteDraft(UUID draftId, UUID deleterUserId); + + /** + * Get available categories for drafts. + * + * @return list of available categories + */ + List getDraftCategories(); + + /** + * Get available tags used in drafts. + * + * @return list of available tags + */ + List getDraftTags(); + + /** + * Get draft statistics for dashboard displays. + * + * @return draft statistics grouped by status + */ + List getDraftStatistics(); + + /** + * Get overdue drafts (target implementation date passed and not published). + * + * @return list of overdue drafts + */ + List getOverdueDrafts(); + + /** + * Clone an existing policy as a new draft for modification. + * + * @param policyId the ID of the policy to clone + * @param creatorUserId the ID of the user creating the draft + * @return the created draft output DTO + */ + PolicyDraftOutputDTO clonePolicyAsDraft(UUID policyId, UUID creatorUserId); + + /** + * Create a new version of an existing draft. + * + * @param draftId the ID of the draft to create new version from + * @param draftInput the updated draft data + * @param creatorUserId the ID of the user creating the new version + * @return the new version draft output DTO + */ + PolicyDraftOutputDTO createDraftVersion(UUID draftId, PolicyDraftInputDTO draftInput, UUID creatorUserId); } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/service/PolicyEvaluationService.java b/src/main/java/com/dalab/policyengine/service/PolicyEvaluationService.java index 9daf5fad2ff3b6f68af31d9acc1b48d4c102506f..8e34f221b6a8f2fd70c06669c72b2ad497274a70 100644 --- a/src/main/java/com/dalab/policyengine/service/PolicyEvaluationService.java +++ b/src/main/java/com/dalab/policyengine/service/PolicyEvaluationService.java @@ -1,295 +1,295 @@ -package com.dalab.policyengine.service; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.jeasy.rules.api.Facts; -import org.jeasy.rules.api.Rule; -import org.jeasy.rules.api.Rules; -import org.jeasy.rules.api.RulesEngine; -import org.jeasy.rules.core.DefaultRulesEngine; -import org.jeasy.rules.mvel.MVELRule; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; - -import com.dalab.common.event.AssetChangeEvent; -import com.dalab.policyengine.common.ResourceNotFoundException; -import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; -import com.dalab.policyengine.dto.PolicyEvaluationRequestDTO; -import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; -import com.dalab.policyengine.kafka.producer.PolicyActionProducer; -import com.dalab.policyengine.mapper.PolicyMapper; -import com.dalab.policyengine.model.Policy; -import com.dalab.policyengine.model.PolicyEvaluation; -import com.dalab.policyengine.model.PolicyEvaluationStatus; -import com.dalab.policyengine.model.PolicyRule; -import com.dalab.policyengine.model.PolicyStatus; -import com.dalab.policyengine.repository.PolicyEvaluationRepository; -import com.dalab.policyengine.repository.PolicyRepository; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.protobuf.Timestamp; - -import jakarta.persistence.criteria.Predicate; - -@Service -@Transactional -public class PolicyEvaluationService implements IPolicyEvaluationService { - - private static final Logger log = LoggerFactory.getLogger(PolicyEvaluationService.class); - - private final PolicyRepository policyRepository; - private final PolicyEvaluationRepository policyEvaluationRepository; - private final PolicyMapper policyMapper; - private final RulesEngine rulesEngine; - private final ObjectMapper objectMapper; // For converting asset data and actions - private final PolicyActionProducer policyActionProducer; - - @Autowired - public PolicyEvaluationService(PolicyRepository policyRepository, - PolicyEvaluationRepository policyEvaluationRepository, - PolicyMapper policyMapper, - ObjectMapper objectMapper, - PolicyActionProducer policyActionProducer) { - this.policyRepository = policyRepository; - this.policyEvaluationRepository = policyEvaluationRepository; - this.policyMapper = policyMapper; - this.objectMapper = objectMapper; - this.rulesEngine = new DefaultRulesEngine(); - this.policyActionProducer = policyActionProducer; - } - - @Override - public PolicyEvaluationOutputDTO evaluatePolicyForAsset(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId) { - Policy policy = policyRepository.findById(policyId) - .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); - - if (policy.getStatus() == PolicyStatus.DISABLED) { - log.warn("Policy {} is disabled. Evaluation skipped for asset {}.", policy.getName(), evaluationRequest.getTargetAssetId()); - PolicyEvaluation evaluation = createEvaluationRecord(policy, evaluationRequest.getTargetAssetId(), PolicyEvaluationStatus.NOT_APPLICABLE, triggeredByUserId, Collections.singletonMap("reason", "Policy disabled"), null); - return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy.getName()); - } - - Map assetData = fetchAssetData(evaluationRequest.getTargetAssetId()); - if (assetData == null || assetData.isEmpty()) { - log.error("Asset data not found for assetId: {}. Evaluation cannot proceed.", evaluationRequest.getTargetAssetId()); - PolicyEvaluation evaluation = createEvaluationRecord(policy, evaluationRequest.getTargetAssetId(), PolicyEvaluationStatus.ERROR, triggeredByUserId, Collections.singletonMap("error", "Asset data not found"), null); - return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy.getName()); - } - - Facts facts = new Facts(); - facts.put("asset", assetData); - if (evaluationRequest.getEvaluationContext() != null) { - evaluationRequest.getEvaluationContext().forEach(facts::put); - } - - Rules easyRules = new Rules(); - Map ruleResults = new HashMap<>(); - for (PolicyRule ruleEntity : policy.getRules()) { - Rule easyRule = new MVELRule() - .name(ruleEntity.getName()) - .description(ruleEntity.getDescription()) - .priority(ruleEntity.getPriority()) - .when(ruleEntity.getCondition()) - .then("ruleResults.put(\"" + ruleEntity.getName() + "\", true); " + - "log.debug(\"Rule '{}' evaluated to true for asset {}\", \"" + ruleEntity.getName() + "\", evaluationRequest.getTargetAssetId());"); - easyRules.register(easyRule); - } - - rulesEngine.fire(easyRules, facts); - - boolean overallPolicyConditionMet = true; - if (StringUtils.hasText(policy.getConditionLogic())) { - overallPolicyConditionMet = ruleResults.values().stream().allMatch(Boolean::booleanValue) && !ruleResults.isEmpty(); - log.debug("Policy condition logic '{}' evaluated to {} based on rule results: {}", policy.getConditionLogic(), overallPolicyConditionMet, ruleResults); - } else { - overallPolicyConditionMet = ruleResults.values().stream().allMatch(Boolean::booleanValue) && !ruleResults.isEmpty(); - log.debug("No policy condition logic. Overall result based on all rules: {}. Rule results: {}", overallPolicyConditionMet, ruleResults); - } - PolicyEvaluationStatus finalStatus = overallPolicyConditionMet ? PolicyEvaluationStatus.PASS : PolicyEvaluationStatus.FAIL; - Map triggeredPolicyActionsSummary = null; - - PolicyEvaluation evaluation = createEvaluationRecord(policy, evaluationRequest.getTargetAssetId(), finalStatus, triggeredByUserId, - Map.of("rulesEvaluated", ruleResults, "factsSnapshot", objectMapper.convertValue(facts.asMap(), new TypeReference>() {})), - null); - - if (finalStatus == PolicyEvaluationStatus.PASS) { - log.info("Policy '{}' PASSED for asset '{}'", policy.getName(), evaluationRequest.getTargetAssetId()); - triggeredPolicyActionsSummary = executePolicyActions(policy, evaluationRequest.getTargetAssetId(), assetData, facts, evaluation.getId()); - evaluation.setTriggeredActions(triggeredPolicyActionsSummary); - policyEvaluationRepository.save(evaluation); - } else { - log.info("Policy '{}' FAILED for asset '{}'", policy.getName(), evaluationRequest.getTargetAssetId()); - } - - return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy.getName()); - } - - @Override - public PolicyEvaluationOutputDTO triggerPolicyEvaluation(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId) { - // Delegate to the main evaluation method - return evaluatePolicyForAsset(policyId, evaluationRequest, triggeredByUserId); - } - - private PolicyEvaluation createEvaluationRecord(Policy policy, String targetAssetId, PolicyEvaluationStatus status, UUID triggeredByUserId, Map details, Map triggeredActions) { - PolicyEvaluation evaluation = new PolicyEvaluation(); - evaluation.setPolicyId(policy.getId()); - evaluation.setTargetAssetId(targetAssetId); - evaluation.setStatus(status); - evaluation.setEvaluationDetails(details); - evaluation.setTriggeredActions(triggeredActions); - evaluation.setEvaluatedAt(Instant.now()); - evaluation.setEvaluationTriggeredByUserId(triggeredByUserId); - return policyEvaluationRepository.save(evaluation); - } - - private Map fetchAssetData(String assetId) { - log.debug("Fetching asset data for assetId: {}", assetId); - Map data = new HashMap<>(); - if ("asset123".equals(assetId)) { - data.put("name", "Sensitive S3 Bucket"); - data.put("assetType", "S3_BUCKET"); - data.put("region", "us-east-1"); - data.put("tags", Arrays.asList("PII", "FinancialData")); - data.put("publicAccess", "true"); - data.put("sizeGB", 1024); - } else if ("asset456".equals(assetId)) { - data.put("name", "Dev EC2 Instance"); - data.put("assetType", "EC2_INSTANCE"); - data.put("region", "eu-west-1"); - data.put("tags", Arrays.asList("Development")); - data.put("instanceType", "t2.micro"); - } - return data.isEmpty() ? null : data; - } - - private Map executePolicyActions(Policy policy, String assetId, Map assetData, Facts facts, UUID evaluationId) { - if (policy.getActions() == null || policy.getActions().isEmpty()) { - return Collections.emptyMap(); - } - log.info("Executing actions for policy '{}' (id={}) on asset '{}': {}", - policy.getName(), policy.getId(), assetId, policy.getActions()); - - Map executedActionsSummary = new HashMap<>(); - Instant now = Instant.now(); - Timestamp eventTimestamp = Timestamp.newBuilder().setSeconds(now.getEpochSecond()).setNanos(now.getNano()).build(); - - final String effectiveAssetId = assetData.getOrDefault("id", assetId).toString(); // Prefer ID from assetData if available - - policy.getActions().forEach((actionType, actionConfig) -> { - // For now, create a simple map-based event instead of protobuf until we add the protobuf definitions - Map policyActionEvent = new HashMap<>(); - policyActionEvent.put("policyId", policy.getId().toString()); - policyActionEvent.put("assetId", effectiveAssetId); - policyActionEvent.put("actionType", actionType); - policyActionEvent.put("timestamp", now.toString()); - - if (evaluationId != null) { - policyActionEvent.put("evaluationId", evaluationId.toString()); - } - - if (actionConfig instanceof Map) { - policyActionEvent.put("actionParameters", actionConfig); - } else if (actionConfig != null) { - policyActionEvent.put("actionParameters", Map.of("value", actionConfig)); - } - - // TODO: Replace with actual protobuf-based PolicyActionEvent when protobuf definitions are added - log.info("Policy action event (simplified): {}", policyActionEvent); - policyActionProducer.sendPolicyActionEvent(policyActionEvent); - - executedActionsSummary.put(actionType, actionConfig); - log.debug("Created PolicyActionEvent for actionType: {} with assetId: {}", actionType, effectiveAssetId); - }); - - return executedActionsSummary; - } - - @Override - @Transactional(readOnly = true) - public Page getPolicyEvaluations(Pageable pageable, UUID policyIdFilter, String targetAssetIdFilter, String statusFilter) { - Specification spec = (root, query, criteriaBuilder) -> { - List predicates = new ArrayList<>(); - if (policyIdFilter != null) { - predicates.add(criteriaBuilder.equal(root.get("policyId"), policyIdFilter)); - } - if (StringUtils.hasText(targetAssetIdFilter)) { - predicates.add(criteriaBuilder.equal(root.get("targetAssetId"), targetAssetIdFilter)); - } - if (StringUtils.hasText(statusFilter)) { - try { - predicates.add(criteriaBuilder.equal(root.get("status"), PolicyEvaluationStatus.valueOf(statusFilter.toUpperCase()))); - } catch (IllegalArgumentException e) { - log.warn("Invalid policy evaluation status provided for filtering: {}", statusFilter); - predicates.add(criteriaBuilder.disjunction()); - } - } - return criteriaBuilder.and(predicates.toArray(new Predicate[0])); - }; - - Page evaluationPage = policyEvaluationRepository.findAll(spec, pageable); - - List policyIds = evaluationPage.getContent().stream() - .map(PolicyEvaluation::getPolicyId) - .distinct() - .collect(Collectors.toList()); - Map policyNamesMap = Collections.emptyMap(); - if (!policyIds.isEmpty()) { - policyNamesMap = policyRepository.findAllById(policyIds).stream() - .collect(Collectors.toMap(Policy::getId, Policy::getName)); - } - final Map finalPolicyNamesMap = policyNamesMap; - - return evaluationPage.map(eval -> policyMapper.toPolicyEvaluationSummaryDTO(eval, finalPolicyNamesMap.getOrDefault(eval.getPolicyId(), "Unknown Policy"))); - } - - @Override - @Transactional(readOnly = true) - public PolicyEvaluationOutputDTO getPolicyEvaluationById(UUID evaluationId) { - PolicyEvaluation evaluation = policyEvaluationRepository.findById(evaluationId) - .orElseThrow(() -> new ResourceNotFoundException("PolicyEvaluation", "id", evaluationId.toString())); - Policy policy = policyRepository.findById(evaluation.getPolicyId()) - .orElse(null); // Policy might be deleted, handle gracefully - return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy != null ? policy.getName() : "Unknown/Deleted Policy"); - } - - @Override - public void evaluatePolicyForAssetInternal(AssetChangeEvent assetChangeEvent, UUID eventInitiatorId) { - String assetIdStr = assetChangeEvent.getAssetId(); - log.info("Processing AssetChangeEvent for assetId: {}, eventType: {}", assetIdStr, assetChangeEvent.getEventType()); - - List activePolicies = policyRepository.findByStatus(PolicyStatus.ENABLED); - if (activePolicies.isEmpty()) { - log.info("No active policies found. No evaluations will be triggered for asset {}", assetIdStr); - return; - } - - log.info("Found {} active policies. Triggering evaluations for asset {}...", activePolicies.size(), assetIdStr); - - for (Policy policy : activePolicies) { - try { - PolicyEvaluationRequestDTO requestDTO = new PolicyEvaluationRequestDTO(); - requestDTO.setTargetAssetId(assetIdStr); - evaluatePolicyForAsset(policy.getId(), requestDTO, eventInitiatorId); - - } catch (Exception e) { - log.error("Error during evaluation of policy {} for asset {}: {}", policy.getName(), assetIdStr, e.getMessage(), e); - } - } - log.info("Finished processing asset change event for assetId: {}", assetIdStr); - } +package com.dalab.policyengine.service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; +import org.jeasy.rules.api.Rules; +import org.jeasy.rules.api.RulesEngine; +import org.jeasy.rules.core.DefaultRulesEngine; +import org.jeasy.rules.mvel.MVELRule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import com.dalab.common.event.AssetChangeEvent; +import com.dalab.policyengine.common.ResourceNotFoundException; +import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; +import com.dalab.policyengine.dto.PolicyEvaluationRequestDTO; +import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; +import com.dalab.policyengine.kafka.producer.PolicyActionProducer; +import com.dalab.policyengine.mapper.PolicyMapper; +import com.dalab.policyengine.model.Policy; +import com.dalab.policyengine.model.PolicyEvaluation; +import com.dalab.policyengine.model.PolicyEvaluationStatus; +import com.dalab.policyengine.model.PolicyRule; +import com.dalab.policyengine.model.PolicyStatus; +import com.dalab.policyengine.repository.PolicyEvaluationRepository; +import com.dalab.policyengine.repository.PolicyRepository; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.Timestamp; + +import jakarta.persistence.criteria.Predicate; + +@Service +@Transactional +public class PolicyEvaluationService implements IPolicyEvaluationService { + + private static final Logger log = LoggerFactory.getLogger(PolicyEvaluationService.class); + + private final PolicyRepository policyRepository; + private final PolicyEvaluationRepository policyEvaluationRepository; + private final PolicyMapper policyMapper; + private final RulesEngine rulesEngine; + private final ObjectMapper objectMapper; // For converting asset data and actions + private final PolicyActionProducer policyActionProducer; + + @Autowired + public PolicyEvaluationService(PolicyRepository policyRepository, + PolicyEvaluationRepository policyEvaluationRepository, + PolicyMapper policyMapper, + ObjectMapper objectMapper, + PolicyActionProducer policyActionProducer) { + this.policyRepository = policyRepository; + this.policyEvaluationRepository = policyEvaluationRepository; + this.policyMapper = policyMapper; + this.objectMapper = objectMapper; + this.rulesEngine = new DefaultRulesEngine(); + this.policyActionProducer = policyActionProducer; + } + + @Override + public PolicyEvaluationOutputDTO evaluatePolicyForAsset(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId) { + Policy policy = policyRepository.findById(policyId) + .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); + + if (policy.getStatus() == PolicyStatus.DISABLED) { + log.warn("Policy {} is disabled. Evaluation skipped for asset {}.", policy.getName(), evaluationRequest.getTargetAssetId()); + PolicyEvaluation evaluation = createEvaluationRecord(policy, evaluationRequest.getTargetAssetId(), PolicyEvaluationStatus.NOT_APPLICABLE, triggeredByUserId, Collections.singletonMap("reason", "Policy disabled"), null); + return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy.getName()); + } + + Map assetData = fetchAssetData(evaluationRequest.getTargetAssetId()); + if (assetData == null || assetData.isEmpty()) { + log.error("Asset data not found for assetId: {}. Evaluation cannot proceed.", evaluationRequest.getTargetAssetId()); + PolicyEvaluation evaluation = createEvaluationRecord(policy, evaluationRequest.getTargetAssetId(), PolicyEvaluationStatus.ERROR, triggeredByUserId, Collections.singletonMap("error", "Asset data not found"), null); + return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy.getName()); + } + + Facts facts = new Facts(); + facts.put("asset", assetData); + if (evaluationRequest.getEvaluationContext() != null) { + evaluationRequest.getEvaluationContext().forEach(facts::put); + } + + Rules easyRules = new Rules(); + Map ruleResults = new HashMap<>(); + for (PolicyRule ruleEntity : policy.getRules()) { + Rule easyRule = new MVELRule() + .name(ruleEntity.getName()) + .description(ruleEntity.getDescription()) + .priority(ruleEntity.getPriority()) + .when(ruleEntity.getCondition()) + .then("ruleResults.put(\"" + ruleEntity.getName() + "\", true); " + + "log.debug(\"Rule '{}' evaluated to true for asset {}\", \"" + ruleEntity.getName() + "\", evaluationRequest.getTargetAssetId());"); + easyRules.register(easyRule); + } + + rulesEngine.fire(easyRules, facts); + + boolean overallPolicyConditionMet = true; + if (StringUtils.hasText(policy.getConditionLogic())) { + overallPolicyConditionMet = ruleResults.values().stream().allMatch(Boolean::booleanValue) && !ruleResults.isEmpty(); + log.debug("Policy condition logic '{}' evaluated to {} based on rule results: {}", policy.getConditionLogic(), overallPolicyConditionMet, ruleResults); + } else { + overallPolicyConditionMet = ruleResults.values().stream().allMatch(Boolean::booleanValue) && !ruleResults.isEmpty(); + log.debug("No policy condition logic. Overall result based on all rules: {}. Rule results: {}", overallPolicyConditionMet, ruleResults); + } + PolicyEvaluationStatus finalStatus = overallPolicyConditionMet ? PolicyEvaluationStatus.PASS : PolicyEvaluationStatus.FAIL; + Map triggeredPolicyActionsSummary = null; + + PolicyEvaluation evaluation = createEvaluationRecord(policy, evaluationRequest.getTargetAssetId(), finalStatus, triggeredByUserId, + Map.of("rulesEvaluated", ruleResults, "factsSnapshot", objectMapper.convertValue(facts.asMap(), new TypeReference>() {})), + null); + + if (finalStatus == PolicyEvaluationStatus.PASS) { + log.info("Policy '{}' PASSED for asset '{}'", policy.getName(), evaluationRequest.getTargetAssetId()); + triggeredPolicyActionsSummary = executePolicyActions(policy, evaluationRequest.getTargetAssetId(), assetData, facts, evaluation.getId()); + evaluation.setTriggeredActions(triggeredPolicyActionsSummary); + policyEvaluationRepository.save(evaluation); + } else { + log.info("Policy '{}' FAILED for asset '{}'", policy.getName(), evaluationRequest.getTargetAssetId()); + } + + return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy.getName()); + } + + @Override + public PolicyEvaluationOutputDTO triggerPolicyEvaluation(UUID policyId, PolicyEvaluationRequestDTO evaluationRequest, UUID triggeredByUserId) { + // Delegate to the main evaluation method + return evaluatePolicyForAsset(policyId, evaluationRequest, triggeredByUserId); + } + + private PolicyEvaluation createEvaluationRecord(Policy policy, String targetAssetId, PolicyEvaluationStatus status, UUID triggeredByUserId, Map details, Map triggeredActions) { + PolicyEvaluation evaluation = new PolicyEvaluation(); + evaluation.setPolicyId(policy.getId()); + evaluation.setTargetAssetId(targetAssetId); + evaluation.setStatus(status); + evaluation.setEvaluationDetails(details); + evaluation.setTriggeredActions(triggeredActions); + evaluation.setEvaluatedAt(Instant.now()); + evaluation.setEvaluationTriggeredByUserId(triggeredByUserId); + return policyEvaluationRepository.save(evaluation); + } + + private Map fetchAssetData(String assetId) { + log.debug("Fetching asset data for assetId: {}", assetId); + Map data = new HashMap<>(); + if ("asset123".equals(assetId)) { + data.put("name", "Sensitive S3 Bucket"); + data.put("assetType", "S3_BUCKET"); + data.put("region", "us-east-1"); + data.put("tags", Arrays.asList("PII", "FinancialData")); + data.put("publicAccess", "true"); + data.put("sizeGB", 1024); + } else if ("asset456".equals(assetId)) { + data.put("name", "Dev EC2 Instance"); + data.put("assetType", "EC2_INSTANCE"); + data.put("region", "eu-west-1"); + data.put("tags", Arrays.asList("Development")); + data.put("instanceType", "t2.micro"); + } + return data.isEmpty() ? null : data; + } + + private Map executePolicyActions(Policy policy, String assetId, Map assetData, Facts facts, UUID evaluationId) { + if (policy.getActions() == null || policy.getActions().isEmpty()) { + return Collections.emptyMap(); + } + log.info("Executing actions for policy '{}' (id={}) on asset '{}': {}", + policy.getName(), policy.getId(), assetId, policy.getActions()); + + Map executedActionsSummary = new HashMap<>(); + Instant now = Instant.now(); + Timestamp eventTimestamp = Timestamp.newBuilder().setSeconds(now.getEpochSecond()).setNanos(now.getNano()).build(); + + final String effectiveAssetId = assetData.getOrDefault("id", assetId).toString(); // Prefer ID from assetData if available + + policy.getActions().forEach((actionType, actionConfig) -> { + // For now, create a simple map-based event instead of protobuf until we add the protobuf definitions + Map policyActionEvent = new HashMap<>(); + policyActionEvent.put("policyId", policy.getId().toString()); + policyActionEvent.put("assetId", effectiveAssetId); + policyActionEvent.put("actionType", actionType); + policyActionEvent.put("timestamp", now.toString()); + + if (evaluationId != null) { + policyActionEvent.put("evaluationId", evaluationId.toString()); + } + + if (actionConfig instanceof Map) { + policyActionEvent.put("actionParameters", actionConfig); + } else if (actionConfig != null) { + policyActionEvent.put("actionParameters", Map.of("value", actionConfig)); + } + + // TODO: Replace with actual protobuf-based PolicyActionEvent when protobuf definitions are added + log.info("Policy action event (simplified): {}", policyActionEvent); + policyActionProducer.sendPolicyActionEvent(policyActionEvent); + + executedActionsSummary.put(actionType, actionConfig); + log.debug("Created PolicyActionEvent for actionType: {} with assetId: {}", actionType, effectiveAssetId); + }); + + return executedActionsSummary; + } + + @Override + @Transactional(readOnly = true) + public Page getPolicyEvaluations(Pageable pageable, UUID policyIdFilter, String targetAssetIdFilter, String statusFilter) { + Specification spec = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + if (policyIdFilter != null) { + predicates.add(criteriaBuilder.equal(root.get("policyId"), policyIdFilter)); + } + if (StringUtils.hasText(targetAssetIdFilter)) { + predicates.add(criteriaBuilder.equal(root.get("targetAssetId"), targetAssetIdFilter)); + } + if (StringUtils.hasText(statusFilter)) { + try { + predicates.add(criteriaBuilder.equal(root.get("status"), PolicyEvaluationStatus.valueOf(statusFilter.toUpperCase()))); + } catch (IllegalArgumentException e) { + log.warn("Invalid policy evaluation status provided for filtering: {}", statusFilter); + predicates.add(criteriaBuilder.disjunction()); + } + } + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + + Page evaluationPage = policyEvaluationRepository.findAll(spec, pageable); + + List policyIds = evaluationPage.getContent().stream() + .map(PolicyEvaluation::getPolicyId) + .distinct() + .collect(Collectors.toList()); + Map policyNamesMap = Collections.emptyMap(); + if (!policyIds.isEmpty()) { + policyNamesMap = policyRepository.findAllById(policyIds).stream() + .collect(Collectors.toMap(Policy::getId, Policy::getName)); + } + final Map finalPolicyNamesMap = policyNamesMap; + + return evaluationPage.map(eval -> policyMapper.toPolicyEvaluationSummaryDTO(eval, finalPolicyNamesMap.getOrDefault(eval.getPolicyId(), "Unknown Policy"))); + } + + @Override + @Transactional(readOnly = true) + public PolicyEvaluationOutputDTO getPolicyEvaluationById(UUID evaluationId) { + PolicyEvaluation evaluation = policyEvaluationRepository.findById(evaluationId) + .orElseThrow(() -> new ResourceNotFoundException("PolicyEvaluation", "id", evaluationId.toString())); + Policy policy = policyRepository.findById(evaluation.getPolicyId()) + .orElse(null); // Policy might be deleted, handle gracefully + return policyMapper.toPolicyEvaluationOutputDTO(evaluation, policy != null ? policy.getName() : "Unknown/Deleted Policy"); + } + + @Override + public void evaluatePolicyForAssetInternal(AssetChangeEvent assetChangeEvent, UUID eventInitiatorId) { + String assetIdStr = assetChangeEvent.getAssetId(); + log.info("Processing AssetChangeEvent for assetId: {}, eventType: {}", assetIdStr, assetChangeEvent.getEventType()); + + List activePolicies = policyRepository.findByStatus(PolicyStatus.ENABLED); + if (activePolicies.isEmpty()) { + log.info("No active policies found. No evaluations will be triggered for asset {}", assetIdStr); + return; + } + + log.info("Found {} active policies. Triggering evaluations for asset {}...", activePolicies.size(), assetIdStr); + + for (Policy policy : activePolicies) { + try { + PolicyEvaluationRequestDTO requestDTO = new PolicyEvaluationRequestDTO(); + requestDTO.setTargetAssetId(assetIdStr); + evaluatePolicyForAsset(policy.getId(), requestDTO, eventInitiatorId); + + } catch (Exception e) { + log.error("Error during evaluation of policy {} for asset {}: {}", policy.getName(), assetIdStr, e.getMessage(), e); + } + } + log.info("Finished processing asset change event for assetId: {}", assetIdStr); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/service/PolicyService.java b/src/main/java/com/dalab/policyengine/service/PolicyService.java index d2780795d07acc3ee6ddaf69afefca67be776e41..4b8a9186540b7f3441b6f2524ff4ceb70047b0d3 100644 --- a/src/main/java/com/dalab/policyengine/service/PolicyService.java +++ b/src/main/java/com/dalab/policyengine/service/PolicyService.java @@ -1,834 +1,834 @@ -package com.dalab.policyengine.service; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; - -import com.dalab.policyengine.common.ConflictException; -import com.dalab.policyengine.common.ResourceNotFoundException; -import com.dalab.policyengine.dto.PolicyDraftActionDTO; -import com.dalab.policyengine.dto.PolicyDraftInputDTO; -import com.dalab.policyengine.dto.PolicyDraftOutputDTO; -import com.dalab.policyengine.dto.PolicyImpactRequestDTO; -import com.dalab.policyengine.dto.PolicyImpactResponseDTO; -// Add missing DTO imports -import com.dalab.policyengine.dto.PolicyInputDTO; -import com.dalab.policyengine.dto.PolicyOutputDTO; -import com.dalab.policyengine.dto.PolicySummaryDTO; -import com.dalab.policyengine.mapper.PolicyDraftMapper; -// Add missing service imports -import com.dalab.policyengine.mapper.PolicyMapper; -// Add missing entity imports -import com.dalab.policyengine.model.Policy; -import com.dalab.policyengine.model.PolicyDraft; -import com.dalab.policyengine.model.PolicyDraftStatus; -import com.dalab.policyengine.model.PolicyStatus; -import com.dalab.policyengine.repository.PolicyDraftRepository; -import com.dalab.policyengine.repository.PolicyRepository; - -// Add missing JPA criteria import -import jakarta.persistence.criteria.Predicate; - -@Service -@Transactional -public class PolicyService implements IPolicyService { - - private static final Logger log = LoggerFactory.getLogger(PolicyService.class); - - private final PolicyRepository policyRepository; - private final PolicyMapper policyMapper; - - // Draft management dependencies - private final PolicyDraftRepository policyDraftRepository; - private final PolicyDraftMapper policyDraftMapper; - - @Autowired - public PolicyService(PolicyRepository policyRepository, PolicyMapper policyMapper, - PolicyDraftRepository policyDraftRepository, PolicyDraftMapper policyDraftMapper) { - this.policyRepository = policyRepository; - this.policyMapper = policyMapper; - this.policyDraftRepository = policyDraftRepository; - this.policyDraftMapper = policyDraftMapper; - } - - @Override - @Transactional(readOnly = true) - public Page getAllPolicies(Pageable pageable, String status, String nameContains) { - Specification spec = (root, query, criteriaBuilder) -> { - List predicates = new ArrayList<>(); - if (StringUtils.hasText(status)) { - try { - predicates.add(criteriaBuilder.equal(root.get("status"), PolicyStatus.valueOf(status.toUpperCase()))); - } catch (IllegalArgumentException e) { - log.warn("Invalid policy status provided: {}", status); - // Option: throw bad request, or ignore filter, or return empty - // Returning empty for now by adding a certainly false predicate - predicates.add(criteriaBuilder.disjunction()); - } - } - if (StringUtils.hasText(nameContains)) { - predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), - "%" + nameContains.toLowerCase() + "%")); - } - return criteriaBuilder.and(predicates.toArray(new Predicate[0])); - }; - return policyRepository.findAll(spec, pageable).map(policyMapper::toPolicySummaryDTO); - } - - @Override - @Transactional(readOnly = true) - public PolicyOutputDTO getPolicyById(UUID policyId) { - Policy policy = policyRepository.findById(policyId) - .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); - return policyMapper.toPolicyOutputDTO(policy); - } - - @Override - public PolicyOutputDTO createPolicy(PolicyInputDTO policyInputDTO, UUID creatorUserId) { - policyRepository.findByName(policyInputDTO.getName()).ifPresent(p -> { - throw new ConflictException("Policy with name '" + policyInputDTO.getName() + "' already exists."); - }); - - Policy policy = policyMapper.toPolicyEntity(policyInputDTO); - policy.setCreatedByUserId(creatorUserId); - // createdAt and updatedAt are set by @PrePersist - - Policy savedPolicy = policyRepository.save(policy); - log.info("Created policy {} with id {}", savedPolicy.getName(), savedPolicy.getId()); - return policyMapper.toPolicyOutputDTO(savedPolicy); - } - - @Override - public PolicyOutputDTO updatePolicy(UUID policyId, PolicyInputDTO policyInputDTO, UUID updaterUserId) { - Policy existingPolicy = policyRepository.findById(policyId) - .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); - - // Check for name conflict if name is being changed - if (!existingPolicy.getName().equals(policyInputDTO.getName())) { - policyRepository.findByName(policyInputDTO.getName()).ifPresent(p -> { - if (!p.getId().equals(existingPolicy.getId())) { - throw new ConflictException("Another policy with name '" + policyInputDTO.getName() + "' already exists."); - } - }); - } - - policyMapper.updatePolicyEntityFromInputDTO(existingPolicy, policyInputDTO); - existingPolicy.setUpdatedByUserId(updaterUserId); - // updatedAt is set by @PreUpdate - - Policy updatedPolicy = policyRepository.save(existingPolicy); - log.info("Updated policy {} with id {}", updatedPolicy.getName(), updatedPolicy.getId()); - return policyMapper.toPolicyOutputDTO(updatedPolicy); - } - - @Override - public void deletePolicy(UUID policyId) { - if (!policyRepository.existsById(policyId)) { - throw new ResourceNotFoundException("Policy", "id", policyId.toString()); - } - // Consider implications: what about active evaluations? Soft delete? - // For now, direct delete. - policyRepository.deleteById(policyId); - log.info("Deleted policy with id {}", policyId); - } - - @Override - @Transactional(readOnly = true) - public PolicyImpactResponseDTO analyzePolicy(PolicyImpactRequestDTO request) { - log.info("Analyzing policy impact for rules content with analysis type: {}", request.getAnalysisType()); - - long startTime = System.currentTimeMillis(); - - // Generate unique analysis ID - String analysisId = "impact-" + UUID.randomUUID().toString().substring(0, 8); - - // Create response with comprehensive mock data - PolicyImpactResponseDTO response = new PolicyImpactResponseDTO(analysisId, request.getAnalysisType()); - - // Build impact summary based on analysis type - PolicyImpactResponseDTO.ImpactSummaryDTO summary = createImpactSummary(request.getAnalysisType()); - response.setSummary(summary); - - // Build affected assets list - List affectedAssets = createAffectedAssetsList(request); - response.setAffectedAssets(affectedAssets); - - // Add performance impact if requested - if (Boolean.TRUE.equals(request.getIncludePerformanceEstimate())) { - PolicyImpactResponseDTO.PerformanceImpactDTO performanceImpact = createPerformanceImpact(request.getAnalysisType()); - response.setPerformanceImpact(performanceImpact); - } - - // Add cost impact if requested - if (Boolean.TRUE.equals(request.getIncludeCostImpact())) { - PolicyImpactResponseDTO.CostImpactDTO costImpact = createCostImpact(request.getAnalysisType()); - response.setCostImpact(costImpact); - } - - // Add compliance impact if requested - if (Boolean.TRUE.equals(request.getIncludeComplianceImpact())) { - PolicyImpactResponseDTO.ComplianceImpactDTO complianceImpact = createComplianceImpact(); - response.setComplianceImpact(complianceImpact); - } - - // Build risk assessment - PolicyImpactResponseDTO.RiskAssessmentDTO riskAssessment = createRiskAssessment(summary); - response.setRiskAssessment(riskAssessment); - - // Build recommendations - List recommendations = createRecommendations(request, summary); - response.setRecommendations(recommendations); - - // Build metadata - long executionTime = System.currentTimeMillis() - startTime; - PolicyImpactResponseDTO.AnalysisMetadataDTO metadata = createAnalysisMetadata(executionTime); - response.setMetadata(metadata); - - log.info("Policy impact analysis completed in {}ms for analysis ID: {}", executionTime, analysisId); - return response; - } - - /** - * Create impact summary based on analysis type with realistic metrics. - */ - private PolicyImpactResponseDTO.ImpactSummaryDTO createImpactSummary(String analysisType) { - PolicyImpactResponseDTO.ImpactSummaryDTO summary = new PolicyImpactResponseDTO.ImpactSummaryDTO(); - - switch (analysisType.toUpperCase()) { - case "FULL": - summary.setTotalAssetsAnalyzed(2847); - summary.setTotalAssetsAffected(1234); - summary.setHighImpactAssets(89); - summary.setMediumImpactAssets(456); - summary.setLowImpactAssets(689); - summary.setOverallRiskLevel("MEDIUM"); - summary.setImpactPercentage(43.4); - break; - case "QUICK": - summary.setTotalAssetsAnalyzed(500); - summary.setTotalAssetsAffected(218); - summary.setHighImpactAssets(15); - summary.setMediumImpactAssets(78); - summary.setLowImpactAssets(125); - summary.setOverallRiskLevel("LOW"); - summary.setImpactPercentage(43.6); - break; - case "TARGETED": - summary.setTotalAssetsAnalyzed(156); - summary.setTotalAssetsAffected(89); - summary.setHighImpactAssets(12); - summary.setMediumImpactAssets(34); - summary.setLowImpactAssets(43); - summary.setOverallRiskLevel("HIGH"); - summary.setImpactPercentage(57.1); - break; - default: - summary.setTotalAssetsAnalyzed(1000); - summary.setTotalAssetsAffected(420); - summary.setHighImpactAssets(35); - summary.setMediumImpactAssets(150); - summary.setLowImpactAssets(235); - summary.setOverallRiskLevel("MEDIUM"); - summary.setImpactPercentage(42.0); - } - - return summary; - } - - /** - * Create list of affected assets with detailed impact information. - */ - private List createAffectedAssetsList(PolicyImpactRequestDTO request) { - List assets = new ArrayList<>(); - - // Sample high-impact asset - PolicyImpactResponseDTO.AssetImpactDTO highImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); - highImpactAsset.setAssetId("asset-db-customer-001"); - highImpactAsset.setAssetName("Customer Database - Production"); - highImpactAsset.setAssetType("database"); - highImpactAsset.setImpactLevel("HIGH"); - highImpactAsset.setAffectedAttributes(Arrays.asList("personal_data", "financial_data", "contact_info")); - highImpactAsset.setAppliedActions(Arrays.asList("auto_encrypt", "access_log", "compliance_tag")); - highImpactAsset.setRiskAssessment("High business impact due to customer data sensitivity and compliance requirements"); - - Map highImpactDetails = new HashMap<>(); - highImpactDetails.put("recordCount", 1250000); - highImpactDetails.put("dataTypes", Arrays.asList("PII", "Financial", "Health")); - highImpactDetails.put("complianceFrameworks", Arrays.asList("GDPR", "PCI-DSS", "HIPAA")); - highImpactAsset.setImpactDetails(highImpactDetails); - assets.add(highImpactAsset); - - // Sample medium-impact asset - PolicyImpactResponseDTO.AssetImpactDTO mediumImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); - mediumImpactAsset.setAssetId("asset-file-logs-002"); - mediumImpactAsset.setAssetName("Application Logs - Analytics"); - mediumImpactAsset.setAssetType("file"); - mediumImpactAsset.setImpactLevel("MEDIUM"); - mediumImpactAsset.setAffectedAttributes(Arrays.asList("user_sessions", "performance_metrics")); - mediumImpactAsset.setAppliedActions(Arrays.asList("retention_policy", "anonymize")); - mediumImpactAsset.setRiskAssessment("Moderate impact on analytics capabilities with potential performance implications"); - - Map mediumImpactDetails = new HashMap<>(); - mediumImpactDetails.put("fileSizeGB", 45.7); - mediumImpactDetails.put("retentionPeriodDays", 90); - mediumImpactDetails.put("accessFrequency", "daily"); - mediumImpactAsset.setImpactDetails(mediumImpactDetails); - assets.add(mediumImpactAsset); - - // Sample low-impact asset - PolicyImpactResponseDTO.AssetImpactDTO lowImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); - lowImpactAsset.setAssetId("asset-api-public-003"); - lowImpactAsset.setAssetName("Public API Documentation"); - lowImpactAsset.setAssetType("api"); - lowImpactAsset.setImpactLevel("LOW"); - lowImpactAsset.setAffectedAttributes(Arrays.asList("endpoint_metadata")); - lowImpactAsset.setAppliedActions(Arrays.asList("classification_tag")); - lowImpactAsset.setRiskAssessment("Minimal impact on public-facing documentation"); - - Map lowImpactDetails = new HashMap<>(); - lowImpactDetails.put("endpointCount", 127); - lowImpactDetails.put("publicAccess", true); - lowImpactDetails.put("lastUpdated", "2024-12-15"); - lowImpactAsset.setImpactDetails(lowImpactDetails); - assets.add(lowImpactAsset); - - return assets; - } - - /** - * Create performance impact estimates based on analysis type. - */ - private PolicyImpactResponseDTO.PerformanceImpactDTO createPerformanceImpact(String analysisType) { - PolicyImpactResponseDTO.PerformanceImpactDTO performance = new PolicyImpactResponseDTO.PerformanceImpactDTO(); - - switch (analysisType.toUpperCase()) { - case "FULL": - performance.setEstimatedProcessingTimeMs(45000L); - performance.setCpuUtilizationIncrease(15.7); - performance.setMemoryUtilizationIncrease(8.3); - performance.setEstimatedApiCalls(2847); - performance.setPerformanceRiskLevel("MEDIUM"); - break; - case "QUICK": - performance.setEstimatedProcessingTimeMs(5200L); - performance.setCpuUtilizationIncrease(3.2); - performance.setMemoryUtilizationIncrease(2.1); - performance.setEstimatedApiCalls(350); - performance.setPerformanceRiskLevel("LOW"); - break; - case "TARGETED": - performance.setEstimatedProcessingTimeMs(8500L); - performance.setCpuUtilizationIncrease(5.8); - performance.setMemoryUtilizationIncrease(3.4); - performance.setEstimatedApiCalls(156); - performance.setPerformanceRiskLevel("LOW"); - break; - default: - performance.setEstimatedProcessingTimeMs(15000L); - performance.setCpuUtilizationIncrease(7.5); - performance.setMemoryUtilizationIncrease(4.2); - performance.setEstimatedApiCalls(1000); - performance.setPerformanceRiskLevel("MEDIUM"); - } - - return performance; - } - - /** - * Create cost impact analysis with estimated costs and savings. - */ - private PolicyImpactResponseDTO.CostImpactDTO createCostImpact(String analysisType) { - PolicyImpactResponseDTO.CostImpactDTO cost = new PolicyImpactResponseDTO.CostImpactDTO(); - - cost.setEstimatedMonthlyCost(2847.50); - cost.setEstimatedImplementationCost(15750.00); - cost.setPotentialSavings(8450.25); - cost.setCostRiskLevel("MEDIUM"); - - Map breakdown = new HashMap<>(); - breakdown.put("compute_resources", 1200.00); - breakdown.put("storage_costs", 675.50); - breakdown.put("api_calls", 425.75); - breakdown.put("compliance_tools", 546.25); - cost.setCostBreakdown(breakdown); - - return cost; - } - - /** - * Create compliance impact analysis. - */ - private PolicyImpactResponseDTO.ComplianceImpactDTO createComplianceImpact() { - PolicyImpactResponseDTO.ComplianceImpactDTO compliance = new PolicyImpactResponseDTO.ComplianceImpactDTO(); - - compliance.setConflictingPolicies(2); - compliance.setComplianceFrameworksAffected(Arrays.asList("GDPR", "PCI-DSS", "SOX", "HIPAA")); - compliance.setComplianceRiskLevel("MEDIUM"); - compliance.setPotentialViolations(Arrays.asList( - "Data retention period conflict with existing archival policy", - "Encryption requirements may override current security policy" - )); - - return compliance; - } - - /** - * Create comprehensive risk assessment. - */ - private PolicyImpactResponseDTO.RiskAssessmentDTO createRiskAssessment(PolicyImpactResponseDTO.ImpactSummaryDTO summary) { - PolicyImpactResponseDTO.RiskAssessmentDTO risk = new PolicyImpactResponseDTO.RiskAssessmentDTO(); - - risk.setOverallRiskLevel(summary.getOverallRiskLevel()); - risk.setRiskScore(calculateRiskScore(summary)); - - List risks = Arrays.asList( - "High-volume data processing may impact system performance", - "Compliance conflicts require manual review and resolution", - "Implementation costs exceed initial estimates", - "Customer data access patterns may be disrupted" - ); - risk.setIdentifiedRisks(risks); - - List mitigations = Arrays.asList( - "Implement gradual rollout with performance monitoring", - "Conduct compliance review before full deployment", - "Establish cost monitoring and alerting mechanisms", - "Create customer communication plan for access changes" - ); - risk.setMitigationStrategies(mitigations); - - return risk; - } - - /** - * Calculate numeric risk score based on impact summary. - */ - private Double calculateRiskScore(PolicyImpactResponseDTO.ImpactSummaryDTO summary) { - double baseScore = 50.0; - - // Adjust based on impact level distribution - if (summary.getHighImpactAssets() != null) { - baseScore += summary.getHighImpactAssets() * 0.5; - } - if (summary.getMediumImpactAssets() != null) { - baseScore += summary.getMediumImpactAssets() * 0.2; - } - - // Adjust based on overall impact percentage - if (summary.getImpactPercentage() != null) { - baseScore += (summary.getImpactPercentage() - 30.0) * 0.8; - } - - // Cap the score between 0 and 100 - return Math.max(0.0, Math.min(100.0, baseScore)); - } - - /** - * Create implementation recommendations based on analysis. - */ - private List createRecommendations(PolicyImpactRequestDTO request, PolicyImpactResponseDTO.ImpactSummaryDTO summary) { - List recommendations = new ArrayList<>(); - - // Risk-based recommendations - switch (summary.getOverallRiskLevel()) { - case "HIGH": - recommendations.add("Consider phased implementation starting with low-impact assets"); - recommendations.add("Establish comprehensive rollback procedures"); - recommendations.add("Increase monitoring and alerting during implementation"); - break; - case "MEDIUM": - recommendations.add("Implement with standard change management procedures"); - recommendations.add("Monitor key performance indicators during rollout"); - break; - case "LOW": - recommendations.add("Proceed with standard implementation timeline"); - recommendations.add("Standard post-implementation review recommended"); - break; - } - - // Analysis type specific recommendations - if ("QUICK".equals(request.getAnalysisType())) { - recommendations.add("Consider running FULL analysis before production deployment"); - } - - // Performance-based recommendations - if (Boolean.TRUE.equals(request.getIncludePerformanceEstimate())) { - recommendations.add("Schedule implementation during low-traffic periods"); - recommendations.add("Prepare additional compute resources for initial processing"); - } - - return recommendations; - } - - /** - * Create analysis execution metadata. - */ - private PolicyImpactResponseDTO.AnalysisMetadataDTO createAnalysisMetadata(long executionTime) { - PolicyImpactResponseDTO.AnalysisMetadataDTO metadata = new PolicyImpactResponseDTO.AnalysisMetadataDTO(); - - metadata.setExecutionTimeMs(executionTime); - metadata.setAnalysisVersion("v2.4.1"); - metadata.setUsesCachedData(false); - metadata.setDataFreshnessTimestamp(Instant.now().minusSeconds(300)); // 5 minutes ago - - return metadata; - } - - // ========== POLICY DRAFT MANAGEMENT IMPLEMENTATION ========== - - @Override - @Transactional - public PolicyDraftOutputDTO createDraft(PolicyDraftInputDTO draftInput, UUID creatorUserId) { - log.info("Creating new policy draft: {}", draftInput.getName()); - - // Validate draft name uniqueness - if (policyDraftRepository.existsByName(draftInput.getName())) { - throw new ConflictException("A draft with this name already exists"); - } - - // Convert DTO to entity - PolicyDraft draft = policyDraftMapper.toEntity(draftInput); - draft.setCreatedByUserId(creatorUserId); - draft.setStatus(PolicyDraftStatus.CREATED); - - // Save the draft - PolicyDraft savedDraft = policyDraftRepository.save(draft); - - log.info("Successfully created policy draft with ID: {}", savedDraft.getId()); - return policyDraftMapper.toOutputDTO(savedDraft); - } - - @Override - @Transactional(readOnly = true) - public PolicyDraftOutputDTO getDraftById(UUID draftId) { - log.debug("Retrieving policy draft with ID: {}", draftId); - - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - return policyDraftMapper.toOutputDTO(draft); - } - - @Override - @Transactional - public PolicyDraftOutputDTO updateDraft(UUID draftId, PolicyDraftInputDTO draftInput, UUID updaterUserId) { - log.info("Updating policy draft with ID: {}", draftId); - - PolicyDraft existingDraft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - // Validate that draft can be updated - if (!canEditDraft(existingDraft.getStatus())) { - throw new ConflictException("Draft cannot be edited in current status: " + existingDraft.getStatus()); - } - - // Validate name uniqueness (excluding current draft) - if (!draftInput.getName().equals(existingDraft.getName()) && - policyDraftRepository.existsByNameExcludingId(draftInput.getName(), draftId)) { - throw new ConflictException("A draft with this name already exists"); - } - - // Update the draft - policyDraftMapper.updateEntity(existingDraft, draftInput); - existingDraft.setUpdatedByUserId(updaterUserId); - - PolicyDraft savedDraft = policyDraftRepository.save(existingDraft); - - log.info("Successfully updated policy draft with ID: {}", savedDraft.getId()); - return policyDraftMapper.toOutputDTO(savedDraft); - } - - @Override - @Transactional - public PolicyDraftOutputDTO submitDraft(UUID draftId, PolicyDraftActionDTO action, UUID submittedByUserId) { - log.info("Submitting policy draft with ID: {}", draftId); - - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - // Validate status transition - if (!canSubmitDraft(draft.getStatus())) { - throw new ConflictException("Draft cannot be submitted in current status: " + draft.getStatus()); - } - - // Update draft status and metadata - draft.setStatus(PolicyDraftStatus.SUBMITTED); - draft.setSubmittedAt(Instant.now()); - draft.setSubmittedByUserId(submittedByUserId); - draft.setUpdatedByUserId(submittedByUserId); - - // Add comment if provided - if (action.getComment() != null && !action.getComment().trim().isEmpty()) { - draft.addReviewComment("Submission: " + action.getComment(), submittedByUserId, "SUBMITTER"); - } - - PolicyDraft savedDraft = policyDraftRepository.save(draft); - - log.info("Successfully submitted policy draft with ID: {}", savedDraft.getId()); - return policyDraftMapper.toOutputDTO(savedDraft); - } - - @Override - @Transactional - public PolicyDraftOutputDTO approveDraft(UUID draftId, PolicyDraftActionDTO action, UUID approverUserId) { - log.info("Approving policy draft with ID: {}", draftId); - - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - // Validate status transition - if (!canApproveDraft(draft.getStatus())) { - throw new ConflictException("Draft cannot be approved in current status: " + draft.getStatus()); - } - - // Update draft status and metadata - draft.setStatus(PolicyDraftStatus.APPROVED); - draft.setApprovedAt(Instant.now()); - draft.setApprovedByUserId(approverUserId); - draft.setUpdatedByUserId(approverUserId); - - // Add comment if provided - if (action.getComment() != null && !action.getComment().trim().isEmpty()) { - draft.addReviewComment("Approval: " + action.getComment(), approverUserId, "APPROVER"); - } - - PolicyDraft savedDraft = policyDraftRepository.save(draft); - - log.info("Successfully approved policy draft with ID: {}", savedDraft.getId()); - return policyDraftMapper.toOutputDTO(savedDraft); - } - - @Override - @Transactional - public PolicyDraftOutputDTO rejectDraft(UUID draftId, PolicyDraftActionDTO action, UUID rejectorUserId) { - log.info("Rejecting policy draft with ID: {}", draftId); - - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - // Validate status transition - if (!canRejectDraft(draft.getStatus())) { - throw new ConflictException("Draft cannot be rejected in current status: " + draft.getStatus()); - } - - // Rejection requires a comment - if (action.getComment() == null || action.getComment().trim().isEmpty()) { - throw new IllegalArgumentException("Comment is required for draft rejection"); - } - - // Update draft status and metadata - draft.setStatus(PolicyDraftStatus.REJECTED); - draft.setRejectedAt(Instant.now()); - draft.setRejectedByUserId(rejectorUserId); - draft.setUpdatedByUserId(rejectorUserId); - - // Add rejection comment - draft.addReviewComment("Rejection: " + action.getComment(), rejectorUserId, "REJECTOR"); - - PolicyDraft savedDraft = policyDraftRepository.save(draft); - - log.info("Successfully rejected policy draft with ID: {}", savedDraft.getId()); - return policyDraftMapper.toOutputDTO(savedDraft); - } - - @Override - @Transactional - public PolicyDraftOutputDTO requestChanges(UUID draftId, PolicyDraftActionDTO action, UUID reviewerUserId) { - log.info("Requesting changes for policy draft with ID: {}", draftId); - - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - // Validate status transition - if (!canRequestChanges(draft.getStatus())) { - throw new ConflictException("Changes cannot be requested for draft in current status: " + draft.getStatus()); - } - - // Change request requires a comment - if (action.getComment() == null || action.getComment().trim().isEmpty()) { - throw new IllegalArgumentException("Comment is required for change request"); - } - - // Update draft status and metadata - draft.setStatus(PolicyDraftStatus.REQUIRES_CHANGES); - draft.setUpdatedByUserId(reviewerUserId); - - // Add change request comment - draft.addReviewComment("Change Request: " + action.getComment(), reviewerUserId, "REVIEWER"); - - PolicyDraft savedDraft = policyDraftRepository.save(draft); - - log.info("Successfully requested changes for policy draft with ID: {}", savedDraft.getId()); - return policyDraftMapper.toOutputDTO(savedDraft); - } - - @Override - @Transactional - public PolicyOutputDTO publishDraft(UUID draftId, PolicyDraftActionDTO action, UUID publisherUserId) { - log.info("Publishing policy draft with ID: {}", draftId); - - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - // Validate status - if (draft.getStatus() != PolicyDraftStatus.APPROVED) { - throw new ConflictException("Only approved drafts can be published"); - } - - // Create policy from draft - PolicyInputDTO policyInput = convertDraftToPolicy(draft); - PolicyOutputDTO publishedPolicy = createPolicy(policyInput, publisherUserId); - - // Update draft status - draft.setStatus(PolicyDraftStatus.PUBLISHED); - draft.setPublishedAt(Instant.now()); - draft.setPublishedByUserId(publisherUserId); - draft.setUpdatedByUserId(publisherUserId); - - // Add publication comment - if (action.getComment() != null && !action.getComment().trim().isEmpty()) { - draft.addReviewComment("Publication: " + action.getComment(), publisherUserId, "PUBLISHER"); - } - - policyDraftRepository.save(draft); - - log.info("Successfully published policy draft {} as policy {}", draftId, publishedPolicy.getId()); - return publishedPolicy; - } - - // Helper methods for validation - private boolean canEditDraft(PolicyDraftStatus status) { - return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; - } - - private boolean canSubmitDraft(PolicyDraftStatus status) { - return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; - } - - private boolean canApproveDraft(PolicyDraftStatus status) { - return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; - } - - private boolean canRejectDraft(PolicyDraftStatus status) { - return status == PolicyDraftStatus.SUBMITTED || - status == PolicyDraftStatus.UNDER_REVIEW || - status == PolicyDraftStatus.APPROVED; - } - - private boolean canRequestChanges(PolicyDraftStatus status) { - return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; - } - - // Convert draft to policy for publication - private PolicyInputDTO convertDraftToPolicy(PolicyDraft draft) { - PolicyInputDTO policyInput = new PolicyInputDTO(); - policyInput.setName(draft.getName()); - policyInput.setDescription(draft.getDescription()); - policyInput.setConditionLogic(draft.getConditionLogic()); - policyInput.setActions(draft.getActions()); - - // Convert rules definition to PolicyRule format if needed - // This is simplified - real implementation would convert JSON rules to PolicyRule entities - - return policyInput; - } - - // Placeholder implementations for remaining methods (to resolve compilation errors) - @Override - public Page getAllDrafts(Pageable pageable, PolicyDraftStatus status, String category, String priority, UUID createdBy, String nameContains) { - // TODO: Implement comprehensive search with filters - Page drafts = policyDraftRepository.findAll(pageable); - return drafts.map(policyDraftMapper::toOutputDTO); - } - - @Override - public Page getDraftsRequiringAttention(UUID userId, Pageable pageable) { - Page drafts = policyDraftRepository.findRequiringAttention(userId, pageable); - return drafts.map(policyDraftMapper::toOutputDTO); - } - - @Override - public Page getDraftsPendingReview(Pageable pageable) { - Page drafts = policyDraftRepository.findPendingReview(pageable); - return drafts.map(policyDraftMapper::toOutputDTO); - } - - @Override - public PolicyDraftOutputDTO archiveDraft(UUID draftId, PolicyDraftActionDTO action, UUID archiverUserId) { - // TODO: Implement archive functionality - throw new UnsupportedOperationException("Archive functionality not yet implemented"); - } - - @Override - public PolicyDraftOutputDTO addReviewComment(UUID draftId, String comment, UUID reviewerUserId, String reviewerRole) { - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - draft.addReviewComment(comment, reviewerUserId, reviewerRole); - PolicyDraft savedDraft = policyDraftRepository.save(draft); - - return policyDraftMapper.toOutputDTO(savedDraft); - } - - @Override - public void deleteDraft(UUID draftId, UUID deleterUserId) { - PolicyDraft draft = policyDraftRepository.findById(draftId) - .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); - - if (draft.getStatus() != PolicyDraftStatus.CREATED) { - throw new ConflictException("Only drafts in CREATED status can be deleted"); - } - - policyDraftRepository.delete(draft); - log.info("Successfully deleted policy draft with ID: {}", draftId); - } - - @Override - public List getDraftCategories() { - return policyDraftRepository.findAllCategories(); - } - - @Override - public List getDraftTags() { - return policyDraftRepository.findAllTags(); - } - - @Override - public List getDraftStatistics() { - return policyDraftRepository.getDraftStatisticsByStatus(); - } - - @Override - public List getOverdueDrafts() { - List overdueDrafts = policyDraftRepository.findOverdueDrafts(Instant.now()); - return overdueDrafts.stream() - .map(policyDraftMapper::toOutputDTO) - .collect(java.util.stream.Collectors.toList()); - } - - @Override - public PolicyDraftOutputDTO clonePolicyAsDraft(UUID policyId, UUID creatorUserId) { - // TODO: Implement policy cloning functionality - throw new UnsupportedOperationException("Policy cloning functionality not yet implemented"); - } - - @Override - public PolicyDraftOutputDTO createDraftVersion(UUID draftId, PolicyDraftInputDTO draftInput, UUID creatorUserId) { - // TODO: Implement versioning functionality - throw new UnsupportedOperationException("Draft versioning functionality not yet implemented"); - } +package com.dalab.policyengine.service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import com.dalab.policyengine.common.ConflictException; +import com.dalab.policyengine.common.ResourceNotFoundException; +import com.dalab.policyengine.dto.PolicyDraftActionDTO; +import com.dalab.policyengine.dto.PolicyDraftInputDTO; +import com.dalab.policyengine.dto.PolicyDraftOutputDTO; +import com.dalab.policyengine.dto.PolicyImpactRequestDTO; +import com.dalab.policyengine.dto.PolicyImpactResponseDTO; +// Add missing DTO imports +import com.dalab.policyengine.dto.PolicyInputDTO; +import com.dalab.policyengine.dto.PolicyOutputDTO; +import com.dalab.policyengine.dto.PolicySummaryDTO; +import com.dalab.policyengine.mapper.PolicyDraftMapper; +// Add missing service imports +import com.dalab.policyengine.mapper.PolicyMapper; +// Add missing entity imports +import com.dalab.policyengine.model.Policy; +import com.dalab.policyengine.model.PolicyDraft; +import com.dalab.policyengine.model.PolicyDraftStatus; +import com.dalab.policyengine.model.PolicyStatus; +import com.dalab.policyengine.repository.PolicyDraftRepository; +import com.dalab.policyengine.repository.PolicyRepository; + +// Add missing JPA criteria import +import jakarta.persistence.criteria.Predicate; + +@Service +@Transactional +public class PolicyService implements IPolicyService { + + private static final Logger log = LoggerFactory.getLogger(PolicyService.class); + + private final PolicyRepository policyRepository; + private final PolicyMapper policyMapper; + + // Draft management dependencies + private final PolicyDraftRepository policyDraftRepository; + private final PolicyDraftMapper policyDraftMapper; + + @Autowired + public PolicyService(PolicyRepository policyRepository, PolicyMapper policyMapper, + PolicyDraftRepository policyDraftRepository, PolicyDraftMapper policyDraftMapper) { + this.policyRepository = policyRepository; + this.policyMapper = policyMapper; + this.policyDraftRepository = policyDraftRepository; + this.policyDraftMapper = policyDraftMapper; + } + + @Override + @Transactional(readOnly = true) + public Page getAllPolicies(Pageable pageable, String status, String nameContains) { + Specification spec = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + if (StringUtils.hasText(status)) { + try { + predicates.add(criteriaBuilder.equal(root.get("status"), PolicyStatus.valueOf(status.toUpperCase()))); + } catch (IllegalArgumentException e) { + log.warn("Invalid policy status provided: {}", status); + // Option: throw bad request, or ignore filter, or return empty + // Returning empty for now by adding a certainly false predicate + predicates.add(criteriaBuilder.disjunction()); + } + } + if (StringUtils.hasText(nameContains)) { + predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), + "%" + nameContains.toLowerCase() + "%")); + } + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + return policyRepository.findAll(spec, pageable).map(policyMapper::toPolicySummaryDTO); + } + + @Override + @Transactional(readOnly = true) + public PolicyOutputDTO getPolicyById(UUID policyId) { + Policy policy = policyRepository.findById(policyId) + .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); + return policyMapper.toPolicyOutputDTO(policy); + } + + @Override + public PolicyOutputDTO createPolicy(PolicyInputDTO policyInputDTO, UUID creatorUserId) { + policyRepository.findByName(policyInputDTO.getName()).ifPresent(p -> { + throw new ConflictException("Policy with name '" + policyInputDTO.getName() + "' already exists."); + }); + + Policy policy = policyMapper.toPolicyEntity(policyInputDTO); + policy.setCreatedByUserId(creatorUserId); + // createdAt and updatedAt are set by @PrePersist + + Policy savedPolicy = policyRepository.save(policy); + log.info("Created policy {} with id {}", savedPolicy.getName(), savedPolicy.getId()); + return policyMapper.toPolicyOutputDTO(savedPolicy); + } + + @Override + public PolicyOutputDTO updatePolicy(UUID policyId, PolicyInputDTO policyInputDTO, UUID updaterUserId) { + Policy existingPolicy = policyRepository.findById(policyId) + .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); + + // Check for name conflict if name is being changed + if (!existingPolicy.getName().equals(policyInputDTO.getName())) { + policyRepository.findByName(policyInputDTO.getName()).ifPresent(p -> { + if (!p.getId().equals(existingPolicy.getId())) { + throw new ConflictException("Another policy with name '" + policyInputDTO.getName() + "' already exists."); + } + }); + } + + policyMapper.updatePolicyEntityFromInputDTO(existingPolicy, policyInputDTO); + existingPolicy.setUpdatedByUserId(updaterUserId); + // updatedAt is set by @PreUpdate + + Policy updatedPolicy = policyRepository.save(existingPolicy); + log.info("Updated policy {} with id {}", updatedPolicy.getName(), updatedPolicy.getId()); + return policyMapper.toPolicyOutputDTO(updatedPolicy); + } + + @Override + public void deletePolicy(UUID policyId) { + if (!policyRepository.existsById(policyId)) { + throw new ResourceNotFoundException("Policy", "id", policyId.toString()); + } + // Consider implications: what about active evaluations? Soft delete? + // For now, direct delete. + policyRepository.deleteById(policyId); + log.info("Deleted policy with id {}", policyId); + } + + @Override + @Transactional(readOnly = true) + public PolicyImpactResponseDTO analyzePolicy(PolicyImpactRequestDTO request) { + log.info("Analyzing policy impact for rules content with analysis type: {}", request.getAnalysisType()); + + long startTime = System.currentTimeMillis(); + + // Generate unique analysis ID + String analysisId = "impact-" + UUID.randomUUID().toString().substring(0, 8); + + // Create response with comprehensive mock data + PolicyImpactResponseDTO response = new PolicyImpactResponseDTO(analysisId, request.getAnalysisType()); + + // Build impact summary based on analysis type + PolicyImpactResponseDTO.ImpactSummaryDTO summary = createImpactSummary(request.getAnalysisType()); + response.setSummary(summary); + + // Build affected assets list + List affectedAssets = createAffectedAssetsList(request); + response.setAffectedAssets(affectedAssets); + + // Add performance impact if requested + if (Boolean.TRUE.equals(request.getIncludePerformanceEstimate())) { + PolicyImpactResponseDTO.PerformanceImpactDTO performanceImpact = createPerformanceImpact(request.getAnalysisType()); + response.setPerformanceImpact(performanceImpact); + } + + // Add cost impact if requested + if (Boolean.TRUE.equals(request.getIncludeCostImpact())) { + PolicyImpactResponseDTO.CostImpactDTO costImpact = createCostImpact(request.getAnalysisType()); + response.setCostImpact(costImpact); + } + + // Add compliance impact if requested + if (Boolean.TRUE.equals(request.getIncludeComplianceImpact())) { + PolicyImpactResponseDTO.ComplianceImpactDTO complianceImpact = createComplianceImpact(); + response.setComplianceImpact(complianceImpact); + } + + // Build risk assessment + PolicyImpactResponseDTO.RiskAssessmentDTO riskAssessment = createRiskAssessment(summary); + response.setRiskAssessment(riskAssessment); + + // Build recommendations + List recommendations = createRecommendations(request, summary); + response.setRecommendations(recommendations); + + // Build metadata + long executionTime = System.currentTimeMillis() - startTime; + PolicyImpactResponseDTO.AnalysisMetadataDTO metadata = createAnalysisMetadata(executionTime); + response.setMetadata(metadata); + + log.info("Policy impact analysis completed in {}ms for analysis ID: {}", executionTime, analysisId); + return response; + } + + /** + * Create impact summary based on analysis type with realistic metrics. + */ + private PolicyImpactResponseDTO.ImpactSummaryDTO createImpactSummary(String analysisType) { + PolicyImpactResponseDTO.ImpactSummaryDTO summary = new PolicyImpactResponseDTO.ImpactSummaryDTO(); + + switch (analysisType.toUpperCase()) { + case "FULL": + summary.setTotalAssetsAnalyzed(2847); + summary.setTotalAssetsAffected(1234); + summary.setHighImpactAssets(89); + summary.setMediumImpactAssets(456); + summary.setLowImpactAssets(689); + summary.setOverallRiskLevel("MEDIUM"); + summary.setImpactPercentage(43.4); + break; + case "QUICK": + summary.setTotalAssetsAnalyzed(500); + summary.setTotalAssetsAffected(218); + summary.setHighImpactAssets(15); + summary.setMediumImpactAssets(78); + summary.setLowImpactAssets(125); + summary.setOverallRiskLevel("LOW"); + summary.setImpactPercentage(43.6); + break; + case "TARGETED": + summary.setTotalAssetsAnalyzed(156); + summary.setTotalAssetsAffected(89); + summary.setHighImpactAssets(12); + summary.setMediumImpactAssets(34); + summary.setLowImpactAssets(43); + summary.setOverallRiskLevel("HIGH"); + summary.setImpactPercentage(57.1); + break; + default: + summary.setTotalAssetsAnalyzed(1000); + summary.setTotalAssetsAffected(420); + summary.setHighImpactAssets(35); + summary.setMediumImpactAssets(150); + summary.setLowImpactAssets(235); + summary.setOverallRiskLevel("MEDIUM"); + summary.setImpactPercentage(42.0); + } + + return summary; + } + + /** + * Create list of affected assets with detailed impact information. + */ + private List createAffectedAssetsList(PolicyImpactRequestDTO request) { + List assets = new ArrayList<>(); + + // Sample high-impact asset + PolicyImpactResponseDTO.AssetImpactDTO highImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); + highImpactAsset.setAssetId("asset-db-customer-001"); + highImpactAsset.setAssetName("Customer Database - Production"); + highImpactAsset.setAssetType("database"); + highImpactAsset.setImpactLevel("HIGH"); + highImpactAsset.setAffectedAttributes(Arrays.asList("personal_data", "financial_data", "contact_info")); + highImpactAsset.setAppliedActions(Arrays.asList("auto_encrypt", "access_log", "compliance_tag")); + highImpactAsset.setRiskAssessment("High business impact due to customer data sensitivity and compliance requirements"); + + Map highImpactDetails = new HashMap<>(); + highImpactDetails.put("recordCount", 1250000); + highImpactDetails.put("dataTypes", Arrays.asList("PII", "Financial", "Health")); + highImpactDetails.put("complianceFrameworks", Arrays.asList("GDPR", "PCI-DSS", "HIPAA")); + highImpactAsset.setImpactDetails(highImpactDetails); + assets.add(highImpactAsset); + + // Sample medium-impact asset + PolicyImpactResponseDTO.AssetImpactDTO mediumImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); + mediumImpactAsset.setAssetId("asset-file-logs-002"); + mediumImpactAsset.setAssetName("Application Logs - Analytics"); + mediumImpactAsset.setAssetType("file"); + mediumImpactAsset.setImpactLevel("MEDIUM"); + mediumImpactAsset.setAffectedAttributes(Arrays.asList("user_sessions", "performance_metrics")); + mediumImpactAsset.setAppliedActions(Arrays.asList("retention_policy", "anonymize")); + mediumImpactAsset.setRiskAssessment("Moderate impact on analytics capabilities with potential performance implications"); + + Map mediumImpactDetails = new HashMap<>(); + mediumImpactDetails.put("fileSizeGB", 45.7); + mediumImpactDetails.put("retentionPeriodDays", 90); + mediumImpactDetails.put("accessFrequency", "daily"); + mediumImpactAsset.setImpactDetails(mediumImpactDetails); + assets.add(mediumImpactAsset); + + // Sample low-impact asset + PolicyImpactResponseDTO.AssetImpactDTO lowImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); + lowImpactAsset.setAssetId("asset-api-public-003"); + lowImpactAsset.setAssetName("Public API Documentation"); + lowImpactAsset.setAssetType("api"); + lowImpactAsset.setImpactLevel("LOW"); + lowImpactAsset.setAffectedAttributes(Arrays.asList("endpoint_metadata")); + lowImpactAsset.setAppliedActions(Arrays.asList("classification_tag")); + lowImpactAsset.setRiskAssessment("Minimal impact on public-facing documentation"); + + Map lowImpactDetails = new HashMap<>(); + lowImpactDetails.put("endpointCount", 127); + lowImpactDetails.put("publicAccess", true); + lowImpactDetails.put("lastUpdated", "2024-12-15"); + lowImpactAsset.setImpactDetails(lowImpactDetails); + assets.add(lowImpactAsset); + + return assets; + } + + /** + * Create performance impact estimates based on analysis type. + */ + private PolicyImpactResponseDTO.PerformanceImpactDTO createPerformanceImpact(String analysisType) { + PolicyImpactResponseDTO.PerformanceImpactDTO performance = new PolicyImpactResponseDTO.PerformanceImpactDTO(); + + switch (analysisType.toUpperCase()) { + case "FULL": + performance.setEstimatedProcessingTimeMs(45000L); + performance.setCpuUtilizationIncrease(15.7); + performance.setMemoryUtilizationIncrease(8.3); + performance.setEstimatedApiCalls(2847); + performance.setPerformanceRiskLevel("MEDIUM"); + break; + case "QUICK": + performance.setEstimatedProcessingTimeMs(5200L); + performance.setCpuUtilizationIncrease(3.2); + performance.setMemoryUtilizationIncrease(2.1); + performance.setEstimatedApiCalls(350); + performance.setPerformanceRiskLevel("LOW"); + break; + case "TARGETED": + performance.setEstimatedProcessingTimeMs(8500L); + performance.setCpuUtilizationIncrease(5.8); + performance.setMemoryUtilizationIncrease(3.4); + performance.setEstimatedApiCalls(156); + performance.setPerformanceRiskLevel("LOW"); + break; + default: + performance.setEstimatedProcessingTimeMs(15000L); + performance.setCpuUtilizationIncrease(7.5); + performance.setMemoryUtilizationIncrease(4.2); + performance.setEstimatedApiCalls(1000); + performance.setPerformanceRiskLevel("MEDIUM"); + } + + return performance; + } + + /** + * Create cost impact analysis with estimated costs and savings. + */ + private PolicyImpactResponseDTO.CostImpactDTO createCostImpact(String analysisType) { + PolicyImpactResponseDTO.CostImpactDTO cost = new PolicyImpactResponseDTO.CostImpactDTO(); + + cost.setEstimatedMonthlyCost(2847.50); + cost.setEstimatedImplementationCost(15750.00); + cost.setPotentialSavings(8450.25); + cost.setCostRiskLevel("MEDIUM"); + + Map breakdown = new HashMap<>(); + breakdown.put("compute_resources", 1200.00); + breakdown.put("storage_costs", 675.50); + breakdown.put("api_calls", 425.75); + breakdown.put("compliance_tools", 546.25); + cost.setCostBreakdown(breakdown); + + return cost; + } + + /** + * Create compliance impact analysis. + */ + private PolicyImpactResponseDTO.ComplianceImpactDTO createComplianceImpact() { + PolicyImpactResponseDTO.ComplianceImpactDTO compliance = new PolicyImpactResponseDTO.ComplianceImpactDTO(); + + compliance.setConflictingPolicies(2); + compliance.setComplianceFrameworksAffected(Arrays.asList("GDPR", "PCI-DSS", "SOX", "HIPAA")); + compliance.setComplianceRiskLevel("MEDIUM"); + compliance.setPotentialViolations(Arrays.asList( + "Data retention period conflict with existing archival policy", + "Encryption requirements may override current security policy" + )); + + return compliance; + } + + /** + * Create comprehensive risk assessment. + */ + private PolicyImpactResponseDTO.RiskAssessmentDTO createRiskAssessment(PolicyImpactResponseDTO.ImpactSummaryDTO summary) { + PolicyImpactResponseDTO.RiskAssessmentDTO risk = new PolicyImpactResponseDTO.RiskAssessmentDTO(); + + risk.setOverallRiskLevel(summary.getOverallRiskLevel()); + risk.setRiskScore(calculateRiskScore(summary)); + + List risks = Arrays.asList( + "High-volume data processing may impact system performance", + "Compliance conflicts require manual review and resolution", + "Implementation costs exceed initial estimates", + "Customer data access patterns may be disrupted" + ); + risk.setIdentifiedRisks(risks); + + List mitigations = Arrays.asList( + "Implement gradual rollout with performance monitoring", + "Conduct compliance review before full deployment", + "Establish cost monitoring and alerting mechanisms", + "Create customer communication plan for access changes" + ); + risk.setMitigationStrategies(mitigations); + + return risk; + } + + /** + * Calculate numeric risk score based on impact summary. + */ + private Double calculateRiskScore(PolicyImpactResponseDTO.ImpactSummaryDTO summary) { + double baseScore = 50.0; + + // Adjust based on impact level distribution + if (summary.getHighImpactAssets() != null) { + baseScore += summary.getHighImpactAssets() * 0.5; + } + if (summary.getMediumImpactAssets() != null) { + baseScore += summary.getMediumImpactAssets() * 0.2; + } + + // Adjust based on overall impact percentage + if (summary.getImpactPercentage() != null) { + baseScore += (summary.getImpactPercentage() - 30.0) * 0.8; + } + + // Cap the score between 0 and 100 + return Math.max(0.0, Math.min(100.0, baseScore)); + } + + /** + * Create implementation recommendations based on analysis. + */ + private List createRecommendations(PolicyImpactRequestDTO request, PolicyImpactResponseDTO.ImpactSummaryDTO summary) { + List recommendations = new ArrayList<>(); + + // Risk-based recommendations + switch (summary.getOverallRiskLevel()) { + case "HIGH": + recommendations.add("Consider phased implementation starting with low-impact assets"); + recommendations.add("Establish comprehensive rollback procedures"); + recommendations.add("Increase monitoring and alerting during implementation"); + break; + case "MEDIUM": + recommendations.add("Implement with standard change management procedures"); + recommendations.add("Monitor key performance indicators during rollout"); + break; + case "LOW": + recommendations.add("Proceed with standard implementation timeline"); + recommendations.add("Standard post-implementation review recommended"); + break; + } + + // Analysis type specific recommendations + if ("QUICK".equals(request.getAnalysisType())) { + recommendations.add("Consider running FULL analysis before production deployment"); + } + + // Performance-based recommendations + if (Boolean.TRUE.equals(request.getIncludePerformanceEstimate())) { + recommendations.add("Schedule implementation during low-traffic periods"); + recommendations.add("Prepare additional compute resources for initial processing"); + } + + return recommendations; + } + + /** + * Create analysis execution metadata. + */ + private PolicyImpactResponseDTO.AnalysisMetadataDTO createAnalysisMetadata(long executionTime) { + PolicyImpactResponseDTO.AnalysisMetadataDTO metadata = new PolicyImpactResponseDTO.AnalysisMetadataDTO(); + + metadata.setExecutionTimeMs(executionTime); + metadata.setAnalysisVersion("v2.4.1"); + metadata.setUsesCachedData(false); + metadata.setDataFreshnessTimestamp(Instant.now().minusSeconds(300)); // 5 minutes ago + + return metadata; + } + + // ========== POLICY DRAFT MANAGEMENT IMPLEMENTATION ========== + + @Override + @Transactional + public PolicyDraftOutputDTO createDraft(PolicyDraftInputDTO draftInput, UUID creatorUserId) { + log.info("Creating new policy draft: {}", draftInput.getName()); + + // Validate draft name uniqueness + if (policyDraftRepository.existsByName(draftInput.getName())) { + throw new ConflictException("A draft with this name already exists"); + } + + // Convert DTO to entity + PolicyDraft draft = policyDraftMapper.toEntity(draftInput); + draft.setCreatedByUserId(creatorUserId); + draft.setStatus(PolicyDraftStatus.CREATED); + + // Save the draft + PolicyDraft savedDraft = policyDraftRepository.save(draft); + + log.info("Successfully created policy draft with ID: {}", savedDraft.getId()); + return policyDraftMapper.toOutputDTO(savedDraft); + } + + @Override + @Transactional(readOnly = true) + public PolicyDraftOutputDTO getDraftById(UUID draftId) { + log.debug("Retrieving policy draft with ID: {}", draftId); + + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + return policyDraftMapper.toOutputDTO(draft); + } + + @Override + @Transactional + public PolicyDraftOutputDTO updateDraft(UUID draftId, PolicyDraftInputDTO draftInput, UUID updaterUserId) { + log.info("Updating policy draft with ID: {}", draftId); + + PolicyDraft existingDraft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + // Validate that draft can be updated + if (!canEditDraft(existingDraft.getStatus())) { + throw new ConflictException("Draft cannot be edited in current status: " + existingDraft.getStatus()); + } + + // Validate name uniqueness (excluding current draft) + if (!draftInput.getName().equals(existingDraft.getName()) && + policyDraftRepository.existsByNameExcludingId(draftInput.getName(), draftId)) { + throw new ConflictException("A draft with this name already exists"); + } + + // Update the draft + policyDraftMapper.updateEntity(existingDraft, draftInput); + existingDraft.setUpdatedByUserId(updaterUserId); + + PolicyDraft savedDraft = policyDraftRepository.save(existingDraft); + + log.info("Successfully updated policy draft with ID: {}", savedDraft.getId()); + return policyDraftMapper.toOutputDTO(savedDraft); + } + + @Override + @Transactional + public PolicyDraftOutputDTO submitDraft(UUID draftId, PolicyDraftActionDTO action, UUID submittedByUserId) { + log.info("Submitting policy draft with ID: {}", draftId); + + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + // Validate status transition + if (!canSubmitDraft(draft.getStatus())) { + throw new ConflictException("Draft cannot be submitted in current status: " + draft.getStatus()); + } + + // Update draft status and metadata + draft.setStatus(PolicyDraftStatus.SUBMITTED); + draft.setSubmittedAt(Instant.now()); + draft.setSubmittedByUserId(submittedByUserId); + draft.setUpdatedByUserId(submittedByUserId); + + // Add comment if provided + if (action.getComment() != null && !action.getComment().trim().isEmpty()) { + draft.addReviewComment("Submission: " + action.getComment(), submittedByUserId, "SUBMITTER"); + } + + PolicyDraft savedDraft = policyDraftRepository.save(draft); + + log.info("Successfully submitted policy draft with ID: {}", savedDraft.getId()); + return policyDraftMapper.toOutputDTO(savedDraft); + } + + @Override + @Transactional + public PolicyDraftOutputDTO approveDraft(UUID draftId, PolicyDraftActionDTO action, UUID approverUserId) { + log.info("Approving policy draft with ID: {}", draftId); + + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + // Validate status transition + if (!canApproveDraft(draft.getStatus())) { + throw new ConflictException("Draft cannot be approved in current status: " + draft.getStatus()); + } + + // Update draft status and metadata + draft.setStatus(PolicyDraftStatus.APPROVED); + draft.setApprovedAt(Instant.now()); + draft.setApprovedByUserId(approverUserId); + draft.setUpdatedByUserId(approverUserId); + + // Add comment if provided + if (action.getComment() != null && !action.getComment().trim().isEmpty()) { + draft.addReviewComment("Approval: " + action.getComment(), approverUserId, "APPROVER"); + } + + PolicyDraft savedDraft = policyDraftRepository.save(draft); + + log.info("Successfully approved policy draft with ID: {}", savedDraft.getId()); + return policyDraftMapper.toOutputDTO(savedDraft); + } + + @Override + @Transactional + public PolicyDraftOutputDTO rejectDraft(UUID draftId, PolicyDraftActionDTO action, UUID rejectorUserId) { + log.info("Rejecting policy draft with ID: {}", draftId); + + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + // Validate status transition + if (!canRejectDraft(draft.getStatus())) { + throw new ConflictException("Draft cannot be rejected in current status: " + draft.getStatus()); + } + + // Rejection requires a comment + if (action.getComment() == null || action.getComment().trim().isEmpty()) { + throw new IllegalArgumentException("Comment is required for draft rejection"); + } + + // Update draft status and metadata + draft.setStatus(PolicyDraftStatus.REJECTED); + draft.setRejectedAt(Instant.now()); + draft.setRejectedByUserId(rejectorUserId); + draft.setUpdatedByUserId(rejectorUserId); + + // Add rejection comment + draft.addReviewComment("Rejection: " + action.getComment(), rejectorUserId, "REJECTOR"); + + PolicyDraft savedDraft = policyDraftRepository.save(draft); + + log.info("Successfully rejected policy draft with ID: {}", savedDraft.getId()); + return policyDraftMapper.toOutputDTO(savedDraft); + } + + @Override + @Transactional + public PolicyDraftOutputDTO requestChanges(UUID draftId, PolicyDraftActionDTO action, UUID reviewerUserId) { + log.info("Requesting changes for policy draft with ID: {}", draftId); + + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + // Validate status transition + if (!canRequestChanges(draft.getStatus())) { + throw new ConflictException("Changes cannot be requested for draft in current status: " + draft.getStatus()); + } + + // Change request requires a comment + if (action.getComment() == null || action.getComment().trim().isEmpty()) { + throw new IllegalArgumentException("Comment is required for change request"); + } + + // Update draft status and metadata + draft.setStatus(PolicyDraftStatus.REQUIRES_CHANGES); + draft.setUpdatedByUserId(reviewerUserId); + + // Add change request comment + draft.addReviewComment("Change Request: " + action.getComment(), reviewerUserId, "REVIEWER"); + + PolicyDraft savedDraft = policyDraftRepository.save(draft); + + log.info("Successfully requested changes for policy draft with ID: {}", savedDraft.getId()); + return policyDraftMapper.toOutputDTO(savedDraft); + } + + @Override + @Transactional + public PolicyOutputDTO publishDraft(UUID draftId, PolicyDraftActionDTO action, UUID publisherUserId) { + log.info("Publishing policy draft with ID: {}", draftId); + + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + // Validate status + if (draft.getStatus() != PolicyDraftStatus.APPROVED) { + throw new ConflictException("Only approved drafts can be published"); + } + + // Create policy from draft + PolicyInputDTO policyInput = convertDraftToPolicy(draft); + PolicyOutputDTO publishedPolicy = createPolicy(policyInput, publisherUserId); + + // Update draft status + draft.setStatus(PolicyDraftStatus.PUBLISHED); + draft.setPublishedAt(Instant.now()); + draft.setPublishedByUserId(publisherUserId); + draft.setUpdatedByUserId(publisherUserId); + + // Add publication comment + if (action.getComment() != null && !action.getComment().trim().isEmpty()) { + draft.addReviewComment("Publication: " + action.getComment(), publisherUserId, "PUBLISHER"); + } + + policyDraftRepository.save(draft); + + log.info("Successfully published policy draft {} as policy {}", draftId, publishedPolicy.getId()); + return publishedPolicy; + } + + // Helper methods for validation + private boolean canEditDraft(PolicyDraftStatus status) { + return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; + } + + private boolean canSubmitDraft(PolicyDraftStatus status) { + return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; + } + + private boolean canApproveDraft(PolicyDraftStatus status) { + return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; + } + + private boolean canRejectDraft(PolicyDraftStatus status) { + return status == PolicyDraftStatus.SUBMITTED || + status == PolicyDraftStatus.UNDER_REVIEW || + status == PolicyDraftStatus.APPROVED; + } + + private boolean canRequestChanges(PolicyDraftStatus status) { + return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; + } + + // Convert draft to policy for publication + private PolicyInputDTO convertDraftToPolicy(PolicyDraft draft) { + PolicyInputDTO policyInput = new PolicyInputDTO(); + policyInput.setName(draft.getName()); + policyInput.setDescription(draft.getDescription()); + policyInput.setConditionLogic(draft.getConditionLogic()); + policyInput.setActions(draft.getActions()); + + // Convert rules definition to PolicyRule format if needed + // This is simplified - real implementation would convert JSON rules to PolicyRule entities + + return policyInput; + } + + // Placeholder implementations for remaining methods (to resolve compilation errors) + @Override + public Page getAllDrafts(Pageable pageable, PolicyDraftStatus status, String category, String priority, UUID createdBy, String nameContains) { + // TODO: Implement comprehensive search with filters + Page drafts = policyDraftRepository.findAll(pageable); + return drafts.map(policyDraftMapper::toOutputDTO); + } + + @Override + public Page getDraftsRequiringAttention(UUID userId, Pageable pageable) { + Page drafts = policyDraftRepository.findRequiringAttention(userId, pageable); + return drafts.map(policyDraftMapper::toOutputDTO); + } + + @Override + public Page getDraftsPendingReview(Pageable pageable) { + Page drafts = policyDraftRepository.findPendingReview(pageable); + return drafts.map(policyDraftMapper::toOutputDTO); + } + + @Override + public PolicyDraftOutputDTO archiveDraft(UUID draftId, PolicyDraftActionDTO action, UUID archiverUserId) { + // TODO: Implement archive functionality + throw new UnsupportedOperationException("Archive functionality not yet implemented"); + } + + @Override + public PolicyDraftOutputDTO addReviewComment(UUID draftId, String comment, UUID reviewerUserId, String reviewerRole) { + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + draft.addReviewComment(comment, reviewerUserId, reviewerRole); + PolicyDraft savedDraft = policyDraftRepository.save(draft); + + return policyDraftMapper.toOutputDTO(savedDraft); + } + + @Override + public void deleteDraft(UUID draftId, UUID deleterUserId) { + PolicyDraft draft = policyDraftRepository.findById(draftId) + .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); + + if (draft.getStatus() != PolicyDraftStatus.CREATED) { + throw new ConflictException("Only drafts in CREATED status can be deleted"); + } + + policyDraftRepository.delete(draft); + log.info("Successfully deleted policy draft with ID: {}", draftId); + } + + @Override + public List getDraftCategories() { + return policyDraftRepository.findAllCategories(); + } + + @Override + public List getDraftTags() { + return policyDraftRepository.findAllTags(); + } + + @Override + public List getDraftStatistics() { + return policyDraftRepository.getDraftStatisticsByStatus(); + } + + @Override + public List getOverdueDrafts() { + List overdueDrafts = policyDraftRepository.findOverdueDrafts(Instant.now()); + return overdueDrafts.stream() + .map(policyDraftMapper::toOutputDTO) + .collect(java.util.stream.Collectors.toList()); + } + + @Override + public PolicyDraftOutputDTO clonePolicyAsDraft(UUID policyId, UUID creatorUserId) { + // TODO: Implement policy cloning functionality + throw new UnsupportedOperationException("Policy cloning functionality not yet implemented"); + } + + @Override + public PolicyDraftOutputDTO createDraftVersion(UUID draftId, PolicyDraftInputDTO draftInput, UUID creatorUserId) { + // TODO: Implement versioning functionality + throw new UnsupportedOperationException("Draft versioning functionality not yet implemented"); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/web/rest/EventCenterController.java b/src/main/java/com/dalab/policyengine/web/rest/EventCenterController.java index 7c9c5db873d0148180a761ae57ddbf64e3902bc6..3477cbc529741d869d6928e6099da662aeaee610 100644 --- a/src/main/java/com/dalab/policyengine/web/rest/EventCenterController.java +++ b/src/main/java/com/dalab/policyengine/web/rest/EventCenterController.java @@ -1,331 +1,331 @@ -package com.dalab.policyengine.web.rest; - -import java.net.URI; -import java.time.Instant; -import java.util.List; -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -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.servlet.support.ServletUriComponentsBuilder; - -import com.dalab.common.security.SecurityUtils; -import com.dalab.policyengine.dto.EventAnalyticsDTO; -import com.dalab.policyengine.dto.EventStreamDTO; -import com.dalab.policyengine.dto.EventSubscriptionInputDTO; -import com.dalab.policyengine.dto.EventSubscriptionOutputDTO; -import com.dalab.policyengine.model.EventSubscriptionStatus; -import com.dalab.policyengine.model.EventType; -import com.dalab.policyengine.service.IEventSubscriptionService; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; - -/** - * REST controller for Event Center functionality. - * Provides endpoints for managing event subscriptions, streaming events, and analytics. - */ -@RestController -@RequestMapping("/api/v1/policyengine/events") -@Tag(name = "Event Center", description = "Endpoints for Event Center management, streaming, and analytics") -public class EventCenterController { - - private static final Logger log = LoggerFactory.getLogger(EventCenterController.class); - - private final IEventSubscriptionService eventSubscriptionService; - - @Autowired - public EventCenterController(IEventSubscriptionService eventSubscriptionService) { - this.eventSubscriptionService = eventSubscriptionService; - } - - // Endpoint 1: Subscription Management - @PostMapping("/subscriptions") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Create event subscription", - description = "Create a new event subscription to receive notifications for specific event types and conditions" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Event subscription created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid subscription configuration"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to create subscription") - }) - public ResponseEntity createSubscription( - @Parameter(description = "Event subscription configuration") - @Valid @RequestBody EventSubscriptionInputDTO inputDTO) { - log.info("REST request to create Event Subscription: {}", inputDTO.getName()); - - UUID creatorUserId = SecurityUtils.getAuthenticatedUserId(); - EventSubscriptionOutputDTO createdSubscription = eventSubscriptionService.createSubscription(inputDTO, creatorUserId); - - URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/{id}") - .buildAndExpand(createdSubscription.getId()) - .toUri(); - - return ResponseEntity.created(location).body(createdSubscription); - } - - @GetMapping("/subscriptions") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Get user's event subscriptions", - description = "Retrieve event subscriptions for the authenticated user with optional filtering" - ) - public ResponseEntity> getUserSubscriptions( - @PageableDefault(size = 20, sort = "name") Pageable pageable, - @RequestParam(required = false) String status, - @RequestParam(required = false) String nameContains) { - log.info("REST request to get Event Subscriptions for user with filters: status={}, nameContains={}", status, nameContains); - - UUID userId = SecurityUtils.getAuthenticatedUserId(); - Page subscriptionsPage = eventSubscriptionService.getSubscriptionsForUser( - userId, pageable, status, nameContains); - - return ResponseEntity.ok(subscriptionsPage); - } - - @GetMapping("/subscriptions/{subscriptionId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Get event subscription by ID", - description = "Retrieve detailed information about a specific event subscription" - ) - public ResponseEntity getSubscriptionById(@PathVariable UUID subscriptionId) { - log.info("REST request to get Event Subscription by id: {}", subscriptionId); - - EventSubscriptionOutputDTO subscription = eventSubscriptionService.getSubscriptionById(subscriptionId); - return ResponseEntity.ok(subscription); - } - - @PutMapping("/subscriptions/{subscriptionId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Update event subscription", - description = "Update configuration of an existing event subscription" - ) - public ResponseEntity updateSubscription( - @PathVariable UUID subscriptionId, - @Valid @RequestBody EventSubscriptionInputDTO inputDTO) { - log.info("REST request to update Event Subscription: {}", subscriptionId); - - UUID updaterUserId = SecurityUtils.getAuthenticatedUserId(); - EventSubscriptionOutputDTO updatedSubscription = eventSubscriptionService.updateSubscription( - subscriptionId, inputDTO, updaterUserId); - - return ResponseEntity.ok(updatedSubscription); - } - - @DeleteMapping("/subscriptions/{subscriptionId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Delete event subscription", - description = "Delete an event subscription and stop receiving notifications" - ) - public ResponseEntity deleteSubscription(@PathVariable UUID subscriptionId) { - log.info("REST request to delete Event Subscription: {}", subscriptionId); - - eventSubscriptionService.deleteSubscription(subscriptionId); - return ResponseEntity.noContent().build(); - } - - @PutMapping("/subscriptions/{subscriptionId}/status") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Update subscription status", - description = "Enable, disable, pause, or archive an event subscription" - ) - public ResponseEntity updateSubscriptionStatus( - @PathVariable UUID subscriptionId, - @RequestParam EventSubscriptionStatus status) { - log.info("REST request to update Event Subscription status: {} to {}", subscriptionId, status); - - UUID updaterUserId = SecurityUtils.getAuthenticatedUserId(); - EventSubscriptionOutputDTO updatedSubscription = eventSubscriptionService.updateSubscriptionStatus( - subscriptionId, status, updaterUserId); - - return ResponseEntity.ok(updatedSubscription); - } - - // Endpoint 2: Event Streaming - @GetMapping("/stream") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Get real-time event stream", - description = "Retrieve real-time events matching user's subscriptions for the Event Center dashboard" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Event stream retrieved successfully"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to access event stream") - }) - public ResponseEntity> getEventStream( - @Parameter(description = "Maximum number of events to return (default: 50)") - @RequestParam(defaultValue = "50") Integer limit) { - log.info("REST request to get Event Stream with limit: {}", limit); - - UUID userId = SecurityUtils.getAuthenticatedUserId(); - List eventStream = eventSubscriptionService.getEventStreamForUser(userId, limit); - - return ResponseEntity.ok(eventStream); - } - - @GetMapping("/stream/all") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") - @Operation( - summary = "Get all event stream (Admin only)", - description = "Retrieve real-time events from all subscriptions across the platform" - ) - public ResponseEntity> getAllEventStream( - @RequestParam(defaultValue = "100") Integer limit) { - log.info("REST request to get All Event Stream with limit: {}", limit); - - List eventStream = eventSubscriptionService.getAllEventStream(limit); - return ResponseEntity.ok(eventStream); - } - - // Endpoint 3: Historical Events - @GetMapping("/subscriptions/{subscriptionId}/history") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Get historical events for subscription", - description = "Retrieve paginated historical events that matched a specific subscription" - ) - public ResponseEntity> getHistoricalEvents( - @PathVariable UUID subscriptionId, - @PageableDefault(size = 50, sort = "timestamp") Pageable pageable) { - log.info("REST request to get Historical Events for subscription: {}", subscriptionId); - - Page historicalEvents = eventSubscriptionService.getHistoricalEventsForSubscription( - subscriptionId, pageable); - - return ResponseEntity.ok(historicalEvents); - } - - // Endpoint 4: Event Analytics - @GetMapping("/analytics") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Get event analytics dashboard", - description = "Retrieve comprehensive analytics for user's event subscriptions including trends, metrics, and insights" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Event analytics retrieved successfully"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to access analytics") - }) - public ResponseEntity getEventAnalytics( - @Parameter(description = "Start time for analytics (defaults to 24 hours ago)") - @RequestParam(required = false) String fromTime, - @Parameter(description = "End time for analytics (defaults to now)") - @RequestParam(required = false) String toTime) { - log.info("REST request to get Event Analytics with time range: {} to {}", fromTime, toTime); - - UUID userId = SecurityUtils.getAuthenticatedUserId(); - - if (fromTime != null && toTime != null) { - Instant from = Instant.parse(fromTime); - Instant to = Instant.parse(toTime); - EventAnalyticsDTO analytics = eventSubscriptionService.getEventAnalyticsForTimeRange(userId, from, to); - return ResponseEntity.ok(analytics); - } else { - EventAnalyticsDTO analytics = eventSubscriptionService.getEventAnalyticsForUser(userId); - return ResponseEntity.ok(analytics); - } - } - - @GetMapping("/analytics/system") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") - @Operation( - summary = "Get system-wide event analytics (Admin only)", - description = "Retrieve comprehensive analytics across all subscriptions and users in the platform" - ) - public ResponseEntity getSystemEventAnalytics() { - log.info("REST request to get System Event Analytics"); - - EventAnalyticsDTO analytics = eventSubscriptionService.getSystemEventAnalytics(); - return ResponseEntity.ok(analytics); - } - - // Endpoint 5: Rule Testing - @PostMapping("/test-rule") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Test event rule condition", - description = "Test an event rule condition against sample event data to validate rule logic before creating subscription" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Rule test completed successfully"), - @ApiResponse(responseCode = "400", description = "Invalid rule condition or sample data") - }) - public ResponseEntity testEventRule( - @Parameter(description = "MVEL rule condition to test") - @RequestParam String ruleCondition, - @Parameter(description = "Sample event data for testing") - @Valid @RequestBody EventStreamDTO sampleEvent) { - log.info("REST request to test Event Rule condition: {}", ruleCondition); - - boolean ruleMatches = eventSubscriptionService.testEventRule(ruleCondition, sampleEvent); - - return ResponseEntity.ok(ruleMatches); - } - - // Endpoint 6: Configuration Helpers - @GetMapping("/config/event-types") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Get available event types", - description = "Retrieve list of all available event types for subscription configuration" - ) - public ResponseEntity> getAvailableEventTypes() { - log.info("REST request to get Available Event Types"); - - List eventTypes = eventSubscriptionService.getAvailableEventTypes(); - return ResponseEntity.ok(eventTypes); - } - - @GetMapping("/config/source-services") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Get available source services", - description = "Retrieve list of all available source services for subscription configuration" - ) - public ResponseEntity> getAvailableSourceServices() { - log.info("REST request to get Available Source Services"); - - List sourceServices = eventSubscriptionService.getAvailableSourceServices(); - return ResponseEntity.ok(sourceServices); - } - - @PostMapping("/config/validate") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - @Operation( - summary = "Validate subscription configuration", - description = "Validate event subscription configuration before creation to catch errors early" - ) - public ResponseEntity validateSubscriptionConfiguration( - @Valid @RequestBody EventSubscriptionInputDTO inputDTO) { - log.info("REST request to validate Subscription Configuration: {}", inputDTO.getName()); - - eventSubscriptionService.validateSubscriptionConfiguration(inputDTO); - return ResponseEntity.ok().build(); - } +package com.dalab.policyengine.web.rest; + +import java.net.URI; +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +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.servlet.support.ServletUriComponentsBuilder; + +import com.dalab.common.security.SecurityUtils; +import com.dalab.policyengine.dto.EventAnalyticsDTO; +import com.dalab.policyengine.dto.EventStreamDTO; +import com.dalab.policyengine.dto.EventSubscriptionInputDTO; +import com.dalab.policyengine.dto.EventSubscriptionOutputDTO; +import com.dalab.policyengine.model.EventSubscriptionStatus; +import com.dalab.policyengine.model.EventType; +import com.dalab.policyengine.service.IEventSubscriptionService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +/** + * REST controller for Event Center functionality. + * Provides endpoints for managing event subscriptions, streaming events, and analytics. + */ +@RestController +@RequestMapping("/api/v1/policyengine/events") +@Tag(name = "Event Center", description = "Endpoints for Event Center management, streaming, and analytics") +public class EventCenterController { + + private static final Logger log = LoggerFactory.getLogger(EventCenterController.class); + + private final IEventSubscriptionService eventSubscriptionService; + + @Autowired + public EventCenterController(IEventSubscriptionService eventSubscriptionService) { + this.eventSubscriptionService = eventSubscriptionService; + } + + // Endpoint 1: Subscription Management + @PostMapping("/subscriptions") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Create event subscription", + description = "Create a new event subscription to receive notifications for specific event types and conditions" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Event subscription created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid subscription configuration"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to create subscription") + }) + public ResponseEntity createSubscription( + @Parameter(description = "Event subscription configuration") + @Valid @RequestBody EventSubscriptionInputDTO inputDTO) { + log.info("REST request to create Event Subscription: {}", inputDTO.getName()); + + UUID creatorUserId = SecurityUtils.getAuthenticatedUserId(); + EventSubscriptionOutputDTO createdSubscription = eventSubscriptionService.createSubscription(inputDTO, creatorUserId); + + URI location = ServletUriComponentsBuilder.fromCurrentRequest() + .path("/{id}") + .buildAndExpand(createdSubscription.getId()) + .toUri(); + + return ResponseEntity.created(location).body(createdSubscription); + } + + @GetMapping("/subscriptions") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Get user's event subscriptions", + description = "Retrieve event subscriptions for the authenticated user with optional filtering" + ) + public ResponseEntity> getUserSubscriptions( + @PageableDefault(size = 20, sort = "name") Pageable pageable, + @RequestParam(required = false) String status, + @RequestParam(required = false) String nameContains) { + log.info("REST request to get Event Subscriptions for user with filters: status={}, nameContains={}", status, nameContains); + + UUID userId = SecurityUtils.getAuthenticatedUserId(); + Page subscriptionsPage = eventSubscriptionService.getSubscriptionsForUser( + userId, pageable, status, nameContains); + + return ResponseEntity.ok(subscriptionsPage); + } + + @GetMapping("/subscriptions/{subscriptionId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Get event subscription by ID", + description = "Retrieve detailed information about a specific event subscription" + ) + public ResponseEntity getSubscriptionById(@PathVariable UUID subscriptionId) { + log.info("REST request to get Event Subscription by id: {}", subscriptionId); + + EventSubscriptionOutputDTO subscription = eventSubscriptionService.getSubscriptionById(subscriptionId); + return ResponseEntity.ok(subscription); + } + + @PutMapping("/subscriptions/{subscriptionId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Update event subscription", + description = "Update configuration of an existing event subscription" + ) + public ResponseEntity updateSubscription( + @PathVariable UUID subscriptionId, + @Valid @RequestBody EventSubscriptionInputDTO inputDTO) { + log.info("REST request to update Event Subscription: {}", subscriptionId); + + UUID updaterUserId = SecurityUtils.getAuthenticatedUserId(); + EventSubscriptionOutputDTO updatedSubscription = eventSubscriptionService.updateSubscription( + subscriptionId, inputDTO, updaterUserId); + + return ResponseEntity.ok(updatedSubscription); + } + + @DeleteMapping("/subscriptions/{subscriptionId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Delete event subscription", + description = "Delete an event subscription and stop receiving notifications" + ) + public ResponseEntity deleteSubscription(@PathVariable UUID subscriptionId) { + log.info("REST request to delete Event Subscription: {}", subscriptionId); + + eventSubscriptionService.deleteSubscription(subscriptionId); + return ResponseEntity.noContent().build(); + } + + @PutMapping("/subscriptions/{subscriptionId}/status") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Update subscription status", + description = "Enable, disable, pause, or archive an event subscription" + ) + public ResponseEntity updateSubscriptionStatus( + @PathVariable UUID subscriptionId, + @RequestParam EventSubscriptionStatus status) { + log.info("REST request to update Event Subscription status: {} to {}", subscriptionId, status); + + UUID updaterUserId = SecurityUtils.getAuthenticatedUserId(); + EventSubscriptionOutputDTO updatedSubscription = eventSubscriptionService.updateSubscriptionStatus( + subscriptionId, status, updaterUserId); + + return ResponseEntity.ok(updatedSubscription); + } + + // Endpoint 2: Event Streaming + @GetMapping("/stream") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Get real-time event stream", + description = "Retrieve real-time events matching user's subscriptions for the Event Center dashboard" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Event stream retrieved successfully"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to access event stream") + }) + public ResponseEntity> getEventStream( + @Parameter(description = "Maximum number of events to return (default: 50)") + @RequestParam(defaultValue = "50") Integer limit) { + log.info("REST request to get Event Stream with limit: {}", limit); + + UUID userId = SecurityUtils.getAuthenticatedUserId(); + List eventStream = eventSubscriptionService.getEventStreamForUser(userId, limit); + + return ResponseEntity.ok(eventStream); + } + + @GetMapping("/stream/all") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") + @Operation( + summary = "Get all event stream (Admin only)", + description = "Retrieve real-time events from all subscriptions across the platform" + ) + public ResponseEntity> getAllEventStream( + @RequestParam(defaultValue = "100") Integer limit) { + log.info("REST request to get All Event Stream with limit: {}", limit); + + List eventStream = eventSubscriptionService.getAllEventStream(limit); + return ResponseEntity.ok(eventStream); + } + + // Endpoint 3: Historical Events + @GetMapping("/subscriptions/{subscriptionId}/history") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Get historical events for subscription", + description = "Retrieve paginated historical events that matched a specific subscription" + ) + public ResponseEntity> getHistoricalEvents( + @PathVariable UUID subscriptionId, + @PageableDefault(size = 50, sort = "timestamp") Pageable pageable) { + log.info("REST request to get Historical Events for subscription: {}", subscriptionId); + + Page historicalEvents = eventSubscriptionService.getHistoricalEventsForSubscription( + subscriptionId, pageable); + + return ResponseEntity.ok(historicalEvents); + } + + // Endpoint 4: Event Analytics + @GetMapping("/analytics") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Get event analytics dashboard", + description = "Retrieve comprehensive analytics for user's event subscriptions including trends, metrics, and insights" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Event analytics retrieved successfully"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to access analytics") + }) + public ResponseEntity getEventAnalytics( + @Parameter(description = "Start time for analytics (defaults to 24 hours ago)") + @RequestParam(required = false) String fromTime, + @Parameter(description = "End time for analytics (defaults to now)") + @RequestParam(required = false) String toTime) { + log.info("REST request to get Event Analytics with time range: {} to {}", fromTime, toTime); + + UUID userId = SecurityUtils.getAuthenticatedUserId(); + + if (fromTime != null && toTime != null) { + Instant from = Instant.parse(fromTime); + Instant to = Instant.parse(toTime); + EventAnalyticsDTO analytics = eventSubscriptionService.getEventAnalyticsForTimeRange(userId, from, to); + return ResponseEntity.ok(analytics); + } else { + EventAnalyticsDTO analytics = eventSubscriptionService.getEventAnalyticsForUser(userId); + return ResponseEntity.ok(analytics); + } + } + + @GetMapping("/analytics/system") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") + @Operation( + summary = "Get system-wide event analytics (Admin only)", + description = "Retrieve comprehensive analytics across all subscriptions and users in the platform" + ) + public ResponseEntity getSystemEventAnalytics() { + log.info("REST request to get System Event Analytics"); + + EventAnalyticsDTO analytics = eventSubscriptionService.getSystemEventAnalytics(); + return ResponseEntity.ok(analytics); + } + + // Endpoint 5: Rule Testing + @PostMapping("/test-rule") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Test event rule condition", + description = "Test an event rule condition against sample event data to validate rule logic before creating subscription" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Rule test completed successfully"), + @ApiResponse(responseCode = "400", description = "Invalid rule condition or sample data") + }) + public ResponseEntity testEventRule( + @Parameter(description = "MVEL rule condition to test") + @RequestParam String ruleCondition, + @Parameter(description = "Sample event data for testing") + @Valid @RequestBody EventStreamDTO sampleEvent) { + log.info("REST request to test Event Rule condition: {}", ruleCondition); + + boolean ruleMatches = eventSubscriptionService.testEventRule(ruleCondition, sampleEvent); + + return ResponseEntity.ok(ruleMatches); + } + + // Endpoint 6: Configuration Helpers + @GetMapping("/config/event-types") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Get available event types", + description = "Retrieve list of all available event types for subscription configuration" + ) + public ResponseEntity> getAvailableEventTypes() { + log.info("REST request to get Available Event Types"); + + List eventTypes = eventSubscriptionService.getAvailableEventTypes(); + return ResponseEntity.ok(eventTypes); + } + + @GetMapping("/config/source-services") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Get available source services", + description = "Retrieve list of all available source services for subscription configuration" + ) + public ResponseEntity> getAvailableSourceServices() { + log.info("REST request to get Available Source Services"); + + List sourceServices = eventSubscriptionService.getAvailableSourceServices(); + return ResponseEntity.ok(sourceServices); + } + + @PostMapping("/config/validate") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + @Operation( + summary = "Validate subscription configuration", + description = "Validate event subscription configuration before creation to catch errors early" + ) + public ResponseEntity validateSubscriptionConfiguration( + @Valid @RequestBody EventSubscriptionInputDTO inputDTO) { + log.info("REST request to validate Subscription Configuration: {}", inputDTO.getName()); + + eventSubscriptionService.validateSubscriptionConfiguration(inputDTO); + return ResponseEntity.ok().build(); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/web/rest/PolicyController.java b/src/main/java/com/dalab/policyengine/web/rest/PolicyController.java index 1860ac24f68a17cd1140d9052926cbd1f874054c..9e3fceef4e954d74d0ea362bf2519acb4617b8aa 100644 --- a/src/main/java/com/dalab/policyengine/web/rest/PolicyController.java +++ b/src/main/java/com/dalab/policyengine/web/rest/PolicyController.java @@ -1,140 +1,140 @@ -package com.dalab.policyengine.web.rest; - -import java.net.URI; -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -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.servlet.support.ServletUriComponentsBuilder; - -import com.dalab.common.security.SecurityUtils; -import com.dalab.policyengine.dto.PolicyImpactRequestDTO; -import com.dalab.policyengine.dto.PolicyImpactResponseDTO; -import com.dalab.policyengine.dto.PolicyInputDTO; -import com.dalab.policyengine.dto.PolicyOutputDTO; -import com.dalab.policyengine.dto.PolicySummaryDTO; -import com.dalab.policyengine.service.IPolicyService; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; - -@RestController -@RequestMapping("/api/v1/policyengine/policies") -@Tag(name = "Policy Management", description = "Endpoints for managing data governance policies") -public class PolicyController { - - private static final Logger log = LoggerFactory.getLogger(PolicyController.class); - - private final IPolicyService policyService; - - @Autowired - public PolicyController(IPolicyService policyService) { - this.policyService = policyService; - } - - @GetMapping - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - public ResponseEntity> getAllPolicies( - @PageableDefault(size = 20, sort = "name") Pageable pageable, - @RequestParam(required = false) String status, - @RequestParam(required = false) String nameContains) { - log.info("REST request to get all Policies with filters: status={}, nameContains={}", status, nameContains); - Page policyPage = policyService.getAllPolicies(pageable, status, nameContains); - return ResponseEntity.ok(policyPage); - } - - @GetMapping("/{policyId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") - public ResponseEntity getPolicyById(@PathVariable UUID policyId) { - log.info("REST request to get Policy by id: {}", policyId); - PolicyOutputDTO policyDTO = policyService.getPolicyById(policyId); - return ResponseEntity.ok(policyDTO); - } - - @PostMapping - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - public ResponseEntity createPolicy(@Valid @RequestBody PolicyInputDTO policyInputDTO) { - log.info("REST request to create Policy: {}", policyInputDTO.getName()); - - UUID creatorUserId = SecurityUtils.getAuthenticatedUserId(); - PolicyOutputDTO createdPolicy = policyService.createPolicy(policyInputDTO, creatorUserId); - - URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/{id}") - .buildAndExpand(createdPolicy.getId()) - .toUri(); - - return ResponseEntity.created(location).body(createdPolicy); - } - - @PutMapping("/{policyId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - public ResponseEntity updatePolicy( - @PathVariable UUID policyId, - @Valid @RequestBody PolicyInputDTO policyInputDTO) { - log.info("REST request to update Policy: {}", policyId); - - UUID updaterUserId = SecurityUtils.getAuthenticatedUserId(); - PolicyOutputDTO updatedPolicy = policyService.updatePolicy(policyId, policyInputDTO, updaterUserId); - - return ResponseEntity.ok(updatedPolicy); - } - - @DeleteMapping("/{policyId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - public ResponseEntity deletePolicy(@PathVariable UUID policyId) { - log.info("REST request to delete Policy by id: {}", policyId); - policyService.deletePolicy(policyId); - return ResponseEntity.noContent().build(); - } - - @PostMapping("/analyze-impact") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") - @Operation( - summary = "Analyze policy impact before implementation", - description = "Provides comprehensive analysis of potential policy impacts including affected assets, performance estimates, cost implications, and compliance analysis. Supports FULL, QUICK, and TARGETED analysis types." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Policy impact analysis completed successfully" - ), - @ApiResponse( - responseCode = "400", - description = "Invalid analysis request parameters" - ), - @ApiResponse( - responseCode = "403", - description = "Insufficient permissions to perform policy analysis" - ) - }) - public ResponseEntity analyzePolicyImpact( - @Parameter(description = "Policy impact analysis request with rules content and analysis parameters") - @Valid @RequestBody PolicyImpactRequestDTO request) { - log.info("REST request to analyze policy impact with analysis type: {}", request.getAnalysisType()); - - PolicyImpactResponseDTO response = policyService.analyzePolicy(request); - - log.info("Policy impact analysis completed for analysis ID: {}", response.getAnalysisId()); - return ResponseEntity.ok(response); - } +package com.dalab.policyengine.web.rest; + +import java.net.URI; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +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.servlet.support.ServletUriComponentsBuilder; + +import com.dalab.common.security.SecurityUtils; +import com.dalab.policyengine.dto.PolicyImpactRequestDTO; +import com.dalab.policyengine.dto.PolicyImpactResponseDTO; +import com.dalab.policyengine.dto.PolicyInputDTO; +import com.dalab.policyengine.dto.PolicyOutputDTO; +import com.dalab.policyengine.dto.PolicySummaryDTO; +import com.dalab.policyengine.service.IPolicyService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/api/v1/policyengine/policies") +@Tag(name = "Policy Management", description = "Endpoints for managing data governance policies") +public class PolicyController { + + private static final Logger log = LoggerFactory.getLogger(PolicyController.class); + + private final IPolicyService policyService; + + @Autowired + public PolicyController(IPolicyService policyService) { + this.policyService = policyService; + } + + @GetMapping + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + public ResponseEntity> getAllPolicies( + @PageableDefault(size = 20, sort = "name") Pageable pageable, + @RequestParam(required = false) String status, + @RequestParam(required = false) String nameContains) { + log.info("REST request to get all Policies with filters: status={}, nameContains={}", status, nameContains); + Page policyPage = policyService.getAllPolicies(pageable, status, nameContains); + return ResponseEntity.ok(policyPage); + } + + @GetMapping("/{policyId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_USER')") + public ResponseEntity getPolicyById(@PathVariable UUID policyId) { + log.info("REST request to get Policy by id: {}", policyId); + PolicyOutputDTO policyDTO = policyService.getPolicyById(policyId); + return ResponseEntity.ok(policyDTO); + } + + @PostMapping + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + public ResponseEntity createPolicy(@Valid @RequestBody PolicyInputDTO policyInputDTO) { + log.info("REST request to create Policy: {}", policyInputDTO.getName()); + + UUID creatorUserId = SecurityUtils.getAuthenticatedUserId(); + PolicyOutputDTO createdPolicy = policyService.createPolicy(policyInputDTO, creatorUserId); + + URI location = ServletUriComponentsBuilder.fromCurrentRequest() + .path("/{id}") + .buildAndExpand(createdPolicy.getId()) + .toUri(); + + return ResponseEntity.created(location).body(createdPolicy); + } + + @PutMapping("/{policyId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + public ResponseEntity updatePolicy( + @PathVariable UUID policyId, + @Valid @RequestBody PolicyInputDTO policyInputDTO) { + log.info("REST request to update Policy: {}", policyId); + + UUID updaterUserId = SecurityUtils.getAuthenticatedUserId(); + PolicyOutputDTO updatedPolicy = policyService.updatePolicy(policyId, policyInputDTO, updaterUserId); + + return ResponseEntity.ok(updatedPolicy); + } + + @DeleteMapping("/{policyId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + public ResponseEntity deletePolicy(@PathVariable UUID policyId) { + log.info("REST request to delete Policy by id: {}", policyId); + policyService.deletePolicy(policyId); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/analyze-impact") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") + @Operation( + summary = "Analyze policy impact before implementation", + description = "Provides comprehensive analysis of potential policy impacts including affected assets, performance estimates, cost implications, and compliance analysis. Supports FULL, QUICK, and TARGETED analysis types." + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Policy impact analysis completed successfully" + ), + @ApiResponse( + responseCode = "400", + description = "Invalid analysis request parameters" + ), + @ApiResponse( + responseCode = "403", + description = "Insufficient permissions to perform policy analysis" + ) + }) + public ResponseEntity analyzePolicyImpact( + @Parameter(description = "Policy impact analysis request with rules content and analysis parameters") + @Valid @RequestBody PolicyImpactRequestDTO request) { + log.info("REST request to analyze policy impact with analysis type: {}", request.getAnalysisType()); + + PolicyImpactResponseDTO response = policyService.analyzePolicy(request); + + log.info("Policy impact analysis completed for analysis ID: {}", response.getAnalysisId()); + return ResponseEntity.ok(response); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/web/rest/PolicyDraftController.java b/src/main/java/com/dalab/policyengine/web/rest/PolicyDraftController.java index eeb8a24737c9038f8a7ef6a7ad593fc97f180778..6a065f1153f6ecc39c6e4b615f9320ad7b9d0a79 100644 --- a/src/main/java/com/dalab/policyengine/web/rest/PolicyDraftController.java +++ b/src/main/java/com/dalab/policyengine/web/rest/PolicyDraftController.java @@ -1,421 +1,421 @@ -package com.dalab.policyengine.web.rest; - -import java.util.List; -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import com.dalab.common.security.SecurityUtils; -import com.dalab.policyengine.dto.PolicyDraftActionDTO; -import com.dalab.policyengine.dto.PolicyDraftInputDTO; -import com.dalab.policyengine.dto.PolicyDraftOutputDTO; -import com.dalab.policyengine.dto.PolicyOutputDTO; -import com.dalab.policyengine.model.PolicyDraftStatus; -import com.dalab.policyengine.service.IPolicyService; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; - -/** - * REST controller for policy draft management operations. - * Provides comprehensive draft workflow including creation, review, approval, and publication. - */ -@RestController -@RequestMapping("/api/v1/policyengine/drafts") -@Tag(name = "Policy Draft Management", description = "Endpoints for managing policy drafts and approval workflows") -public class PolicyDraftController { - - private static final Logger log = LoggerFactory.getLogger(PolicyDraftController.class); - - private final IPolicyService policyService; - - @Autowired - public PolicyDraftController(IPolicyService policyService) { - this.policyService = policyService; - } - - @PostMapping - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") - @Operation( - summary = "Create a new policy draft", - description = "Creates a new policy draft that can be edited, submitted for review, and eventually published as an active policy." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Policy draft created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid draft data provided"), - @ApiResponse(responseCode = "409", description = "Draft with this name already exists"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to create drafts") - }) - public ResponseEntity createDraft( - @Parameter(description = "Policy draft creation data") - @Valid @RequestBody PolicyDraftInputDTO draftInput) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Creating policy draft: {} by user: {}", draftInput.getName(), currentUserId); - - PolicyDraftOutputDTO createdDraft = policyService.createDraft(draftInput, currentUserId); - - return ResponseEntity.status(HttpStatus.CREATED).body(createdDraft); - } - - @GetMapping - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") - @Operation( - summary = "Get all policy drafts with filtering", - description = "Retrieves paginated list of policy drafts with optional filtering by status, category, priority, creator, and name search." - ) - public ResponseEntity> getAllDrafts( - @PageableDefault(size = 20, sort = "updatedAt", direction = Sort.Direction.DESC) Pageable pageable, - @Parameter(description = "Filter by draft status") @RequestParam(required = false) PolicyDraftStatus status, - @Parameter(description = "Filter by category") @RequestParam(required = false) String category, - @Parameter(description = "Filter by priority level") @RequestParam(required = false) String priority, - @Parameter(description = "Filter by creator user ID") @RequestParam(required = false) UUID createdBy, - @Parameter(description = "Search in draft names") @RequestParam(required = false) String nameContains) { - - log.debug("Retrieving policy drafts with filters - status: {}, category: {}, priority: {}", - status, category, priority); - - Page drafts = policyService.getAllDrafts( - pageable, status, category, priority, createdBy, nameContains); - - return ResponseEntity.ok(drafts); - } - - @GetMapping("/{draftId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") - @Operation( - summary = "Get policy draft by ID", - description = "Retrieves a specific policy draft with complete workflow information and available actions." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Policy draft retrieved successfully"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to view draft") - }) - public ResponseEntity getDraftById( - @Parameter(description = "Draft ID") @PathVariable UUID draftId) { - - log.debug("Retrieving policy draft with ID: {}", draftId); - - PolicyDraftOutputDTO draft = policyService.getDraftById(draftId); - - return ResponseEntity.ok(draft); - } - - @PutMapping("/{draftId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") - @Operation( - summary = "Update policy draft", - description = "Updates an existing policy draft. Only allowed for drafts in CREATED or REQUIRES_CHANGES status." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Policy draft updated successfully"), - @ApiResponse(responseCode = "400", description = "Invalid draft data provided"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "409", description = "Draft cannot be updated in current status or name conflict"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to update draft") - }) - public ResponseEntity updateDraft( - @Parameter(description = "Draft ID") @PathVariable UUID draftId, - @Parameter(description = "Updated draft data") @Valid @RequestBody PolicyDraftInputDTO draftInput) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Updating policy draft: {} by user: {}", draftId, currentUserId); - - PolicyDraftOutputDTO updatedDraft = policyService.updateDraft(draftId, draftInput, currentUserId); - - return ResponseEntity.ok(updatedDraft); - } - - @DeleteMapping("/{draftId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Delete policy draft", - description = "Deletes a policy draft. Only allowed for drafts in CREATED status." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Policy draft deleted successfully"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "409", description = "Draft cannot be deleted in current status"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to delete draft") - }) - public ResponseEntity deleteDraft( - @Parameter(description = "Draft ID") @PathVariable UUID draftId) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Deleting policy draft: {} by user: {}", draftId, currentUserId); - - policyService.deleteDraft(draftId, currentUserId); - - return ResponseEntity.noContent().build(); - } - - // ========== WORKFLOW ACTION ENDPOINTS ========== - - @PostMapping("/{draftId}/submit") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") - @Operation( - summary = "Submit draft for review", - description = "Submits a policy draft for review, transitioning it from CREATED or REQUIRES_CHANGES to SUBMITTED status." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Draft submitted successfully"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "409", description = "Draft cannot be submitted in current status"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to submit draft") - }) - public ResponseEntity submitDraft( - @Parameter(description = "Draft ID") @PathVariable UUID draftId, - @Parameter(description = "Submission action with optional comment") @Valid @RequestBody PolicyDraftActionDTO action) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Submitting policy draft: {} by user: {}", draftId, currentUserId); - - PolicyDraftOutputDTO submittedDraft = policyService.submitDraft(draftId, action, currentUserId); - - return ResponseEntity.ok(submittedDraft); - } - - @PostMapping("/{draftId}/approve") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Approve policy draft", - description = "Approves a policy draft, transitioning it to APPROVED status and making it ready for publication." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Draft approved successfully"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "409", description = "Draft cannot be approved in current status"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to approve draft") - }) - public ResponseEntity approveDraft( - @Parameter(description = "Draft ID") @PathVariable UUID draftId, - @Parameter(description = "Approval action with optional comment") @Valid @RequestBody PolicyDraftActionDTO action) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Approving policy draft: {} by user: {}", draftId, currentUserId); - - PolicyDraftOutputDTO approvedDraft = policyService.approveDraft(draftId, action, currentUserId); - - return ResponseEntity.ok(approvedDraft); - } - - @PostMapping("/{draftId}/reject") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Reject policy draft", - description = "Rejects a policy draft, transitioning it to REJECTED status. A comment explaining the rejection is required." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Draft rejected successfully"), - @ApiResponse(responseCode = "400", description = "Comment is required for rejection"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "409", description = "Draft cannot be rejected in current status"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to reject draft") - }) - public ResponseEntity rejectDraft( - @Parameter(description = "Draft ID") @PathVariable UUID draftId, - @Parameter(description = "Rejection action with required comment") @Valid @RequestBody PolicyDraftActionDTO action) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Rejecting policy draft: {} by user: {}", draftId, currentUserId); - - PolicyDraftOutputDTO rejectedDraft = policyService.rejectDraft(draftId, action, currentUserId); - - return ResponseEntity.ok(rejectedDraft); - } - - @PostMapping("/{draftId}/request-changes") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Request changes to draft", - description = "Requests changes to a policy draft, transitioning it to REQUIRES_CHANGES status. A comment explaining required changes is mandatory." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Changes requested successfully"), - @ApiResponse(responseCode = "400", description = "Comment is required for change request"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "409", description = "Changes cannot be requested for draft in current status"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to request changes") - }) - public ResponseEntity requestChanges( - @Parameter(description = "Draft ID") @PathVariable UUID draftId, - @Parameter(description = "Change request action with required comment") @Valid @RequestBody PolicyDraftActionDTO action) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Requesting changes for policy draft: {} by user: {}", draftId, currentUserId); - - PolicyDraftOutputDTO updatedDraft = policyService.requestChanges(draftId, action, currentUserId); - - return ResponseEntity.ok(updatedDraft); - } - - @PostMapping("/{draftId}/publish") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Publish approved draft as policy", - description = "Publishes an approved policy draft as an active policy. Only APPROVED drafts can be published." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Draft published successfully"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "409", description = "Only approved drafts can be published"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to publish draft") - }) - public ResponseEntity publishDraft( - @Parameter(description = "Draft ID") @PathVariable UUID draftId, - @Parameter(description = "Publication action with optional metadata") @Valid @RequestBody PolicyDraftActionDTO action) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.info("Publishing policy draft: {} by user: {}", draftId, currentUserId); - - PolicyOutputDTO publishedPolicy = policyService.publishDraft(draftId, action, currentUserId); - - return ResponseEntity.ok(publishedPolicy); - } - - @PostMapping("/{draftId}/comments") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") - @Operation( - summary = "Add review comment to draft", - description = "Adds a review comment to a policy draft for collaboration and feedback purposes." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Comment added successfully"), - @ApiResponse(responseCode = "400", description = "Invalid comment data"), - @ApiResponse(responseCode = "404", description = "Policy draft not found"), - @ApiResponse(responseCode = "403", description = "Insufficient permissions to comment on draft") - }) - public ResponseEntity addComment( - @Parameter(description = "Draft ID") @PathVariable UUID draftId, - @Parameter(description = "Review comment") @RequestParam String comment) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - String userRole = "REVIEWER"; // Simplified - would get actual roles from security context - - log.debug("Adding comment to policy draft: {} by user: {}", draftId, currentUserId); - - PolicyDraftOutputDTO updatedDraft = policyService.addReviewComment(draftId, comment, currentUserId, userRole); - - return ResponseEntity.ok(updatedDraft); - } - - // ========== SPECIALIZED QUERY ENDPOINTS ========== - - @GetMapping("/my-drafts") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") - @Operation( - summary = "Get drafts requiring my attention", - description = "Retrieves drafts that require attention from the current user (created by them or they are a stakeholder)." - ) - public ResponseEntity> getMyDrafts( - @PageableDefault(size = 20, sort = "updatedAt", direction = Sort.Direction.DESC) Pageable pageable) { - - UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); - log.debug("Retrieving drafts requiring attention for user: {}", currentUserId); - - Page drafts = policyService.getDraftsRequiringAttention(currentUserId, pageable); - - return ResponseEntity.ok(drafts); - } - - @GetMapping("/pending-review") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Get drafts pending review", - description = "Retrieves all drafts that are currently pending review (SUBMITTED or UNDER_REVIEW status)." - ) - public ResponseEntity> getDraftsPendingReview( - @PageableDefault(size = 20, sort = "submittedAt", direction = Sort.Direction.ASC) Pageable pageable) { - - log.debug("Retrieving drafts pending review"); - - Page drafts = policyService.getDraftsPendingReview(pageable); - - return ResponseEntity.ok(drafts); - } - - @GetMapping("/overdue") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Get overdue drafts", - description = "Retrieves drafts that have passed their target implementation date but haven't been published yet." - ) - public ResponseEntity> getOverdueDrafts() { - - log.debug("Retrieving overdue drafts"); - - List overdueDrafts = policyService.getOverdueDrafts(); - - return ResponseEntity.ok(overdueDrafts); - } - - // ========== METADATA ENDPOINTS ========== - - @GetMapping("/categories") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") - @Operation( - summary = "Get available draft categories", - description = "Retrieves list of all categories used in policy drafts for filtering and organization." - ) - public ResponseEntity> getDraftCategories() { - - log.debug("Retrieving draft categories"); - - List categories = policyService.getDraftCategories(); - - return ResponseEntity.ok(categories); - } - - @GetMapping("/tags") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") - @Operation( - summary = "Get available draft tags", - description = "Retrieves list of all tags used in policy drafts for filtering and organization." - ) - public ResponseEntity> getDraftTags() { - - log.debug("Retrieving draft tags"); - - List tags = policyService.getDraftTags(); - - return ResponseEntity.ok(tags); - } - - @GetMapping("/statistics") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") - @Operation( - summary = "Get draft statistics", - description = "Retrieves statistical information about policy drafts grouped by status for dashboard displays." - ) - public ResponseEntity> getDraftStatistics() { - - log.debug("Retrieving draft statistics"); - - List statistics = policyService.getDraftStatistics(); - - return ResponseEntity.ok(statistics); - } +package com.dalab.policyengine.web.rest; + +import java.util.List; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.dalab.common.security.SecurityUtils; +import com.dalab.policyengine.dto.PolicyDraftActionDTO; +import com.dalab.policyengine.dto.PolicyDraftInputDTO; +import com.dalab.policyengine.dto.PolicyDraftOutputDTO; +import com.dalab.policyengine.dto.PolicyOutputDTO; +import com.dalab.policyengine.model.PolicyDraftStatus; +import com.dalab.policyengine.service.IPolicyService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +/** + * REST controller for policy draft management operations. + * Provides comprehensive draft workflow including creation, review, approval, and publication. + */ +@RestController +@RequestMapping("/api/v1/policyengine/drafts") +@Tag(name = "Policy Draft Management", description = "Endpoints for managing policy drafts and approval workflows") +public class PolicyDraftController { + + private static final Logger log = LoggerFactory.getLogger(PolicyDraftController.class); + + private final IPolicyService policyService; + + @Autowired + public PolicyDraftController(IPolicyService policyService) { + this.policyService = policyService; + } + + @PostMapping + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") + @Operation( + summary = "Create a new policy draft", + description = "Creates a new policy draft that can be edited, submitted for review, and eventually published as an active policy." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Policy draft created successfully"), + @ApiResponse(responseCode = "400", description = "Invalid draft data provided"), + @ApiResponse(responseCode = "409", description = "Draft with this name already exists"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to create drafts") + }) + public ResponseEntity createDraft( + @Parameter(description = "Policy draft creation data") + @Valid @RequestBody PolicyDraftInputDTO draftInput) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Creating policy draft: {} by user: {}", draftInput.getName(), currentUserId); + + PolicyDraftOutputDTO createdDraft = policyService.createDraft(draftInput, currentUserId); + + return ResponseEntity.status(HttpStatus.CREATED).body(createdDraft); + } + + @GetMapping + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") + @Operation( + summary = "Get all policy drafts with filtering", + description = "Retrieves paginated list of policy drafts with optional filtering by status, category, priority, creator, and name search." + ) + public ResponseEntity> getAllDrafts( + @PageableDefault(size = 20, sort = "updatedAt", direction = Sort.Direction.DESC) Pageable pageable, + @Parameter(description = "Filter by draft status") @RequestParam(required = false) PolicyDraftStatus status, + @Parameter(description = "Filter by category") @RequestParam(required = false) String category, + @Parameter(description = "Filter by priority level") @RequestParam(required = false) String priority, + @Parameter(description = "Filter by creator user ID") @RequestParam(required = false) UUID createdBy, + @Parameter(description = "Search in draft names") @RequestParam(required = false) String nameContains) { + + log.debug("Retrieving policy drafts with filters - status: {}, category: {}, priority: {}", + status, category, priority); + + Page drafts = policyService.getAllDrafts( + pageable, status, category, priority, createdBy, nameContains); + + return ResponseEntity.ok(drafts); + } + + @GetMapping("/{draftId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") + @Operation( + summary = "Get policy draft by ID", + description = "Retrieves a specific policy draft with complete workflow information and available actions." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Policy draft retrieved successfully"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to view draft") + }) + public ResponseEntity getDraftById( + @Parameter(description = "Draft ID") @PathVariable UUID draftId) { + + log.debug("Retrieving policy draft with ID: {}", draftId); + + PolicyDraftOutputDTO draft = policyService.getDraftById(draftId); + + return ResponseEntity.ok(draft); + } + + @PutMapping("/{draftId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") + @Operation( + summary = "Update policy draft", + description = "Updates an existing policy draft. Only allowed for drafts in CREATED or REQUIRES_CHANGES status." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Policy draft updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid draft data provided"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "409", description = "Draft cannot be updated in current status or name conflict"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to update draft") + }) + public ResponseEntity updateDraft( + @Parameter(description = "Draft ID") @PathVariable UUID draftId, + @Parameter(description = "Updated draft data") @Valid @RequestBody PolicyDraftInputDTO draftInput) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Updating policy draft: {} by user: {}", draftId, currentUserId); + + PolicyDraftOutputDTO updatedDraft = policyService.updateDraft(draftId, draftInput, currentUserId); + + return ResponseEntity.ok(updatedDraft); + } + + @DeleteMapping("/{draftId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Delete policy draft", + description = "Deletes a policy draft. Only allowed for drafts in CREATED status." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Policy draft deleted successfully"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "409", description = "Draft cannot be deleted in current status"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to delete draft") + }) + public ResponseEntity deleteDraft( + @Parameter(description = "Draft ID") @PathVariable UUID draftId) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Deleting policy draft: {} by user: {}", draftId, currentUserId); + + policyService.deleteDraft(draftId, currentUserId); + + return ResponseEntity.noContent().build(); + } + + // ========== WORKFLOW ACTION ENDPOINTS ========== + + @PostMapping("/{draftId}/submit") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") + @Operation( + summary = "Submit draft for review", + description = "Submits a policy draft for review, transitioning it from CREATED or REQUIRES_CHANGES to SUBMITTED status." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Draft submitted successfully"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "409", description = "Draft cannot be submitted in current status"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to submit draft") + }) + public ResponseEntity submitDraft( + @Parameter(description = "Draft ID") @PathVariable UUID draftId, + @Parameter(description = "Submission action with optional comment") @Valid @RequestBody PolicyDraftActionDTO action) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Submitting policy draft: {} by user: {}", draftId, currentUserId); + + PolicyDraftOutputDTO submittedDraft = policyService.submitDraft(draftId, action, currentUserId); + + return ResponseEntity.ok(submittedDraft); + } + + @PostMapping("/{draftId}/approve") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Approve policy draft", + description = "Approves a policy draft, transitioning it to APPROVED status and making it ready for publication." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Draft approved successfully"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "409", description = "Draft cannot be approved in current status"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to approve draft") + }) + public ResponseEntity approveDraft( + @Parameter(description = "Draft ID") @PathVariable UUID draftId, + @Parameter(description = "Approval action with optional comment") @Valid @RequestBody PolicyDraftActionDTO action) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Approving policy draft: {} by user: {}", draftId, currentUserId); + + PolicyDraftOutputDTO approvedDraft = policyService.approveDraft(draftId, action, currentUserId); + + return ResponseEntity.ok(approvedDraft); + } + + @PostMapping("/{draftId}/reject") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Reject policy draft", + description = "Rejects a policy draft, transitioning it to REJECTED status. A comment explaining the rejection is required." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Draft rejected successfully"), + @ApiResponse(responseCode = "400", description = "Comment is required for rejection"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "409", description = "Draft cannot be rejected in current status"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to reject draft") + }) + public ResponseEntity rejectDraft( + @Parameter(description = "Draft ID") @PathVariable UUID draftId, + @Parameter(description = "Rejection action with required comment") @Valid @RequestBody PolicyDraftActionDTO action) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Rejecting policy draft: {} by user: {}", draftId, currentUserId); + + PolicyDraftOutputDTO rejectedDraft = policyService.rejectDraft(draftId, action, currentUserId); + + return ResponseEntity.ok(rejectedDraft); + } + + @PostMapping("/{draftId}/request-changes") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Request changes to draft", + description = "Requests changes to a policy draft, transitioning it to REQUIRES_CHANGES status. A comment explaining required changes is mandatory." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Changes requested successfully"), + @ApiResponse(responseCode = "400", description = "Comment is required for change request"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "409", description = "Changes cannot be requested for draft in current status"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to request changes") + }) + public ResponseEntity requestChanges( + @Parameter(description = "Draft ID") @PathVariable UUID draftId, + @Parameter(description = "Change request action with required comment") @Valid @RequestBody PolicyDraftActionDTO action) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Requesting changes for policy draft: {} by user: {}", draftId, currentUserId); + + PolicyDraftOutputDTO updatedDraft = policyService.requestChanges(draftId, action, currentUserId); + + return ResponseEntity.ok(updatedDraft); + } + + @PostMapping("/{draftId}/publish") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Publish approved draft as policy", + description = "Publishes an approved policy draft as an active policy. Only APPROVED drafts can be published." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Draft published successfully"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "409", description = "Only approved drafts can be published"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to publish draft") + }) + public ResponseEntity publishDraft( + @Parameter(description = "Draft ID") @PathVariable UUID draftId, + @Parameter(description = "Publication action with optional metadata") @Valid @RequestBody PolicyDraftActionDTO action) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.info("Publishing policy draft: {} by user: {}", draftId, currentUserId); + + PolicyOutputDTO publishedPolicy = policyService.publishDraft(draftId, action, currentUserId); + + return ResponseEntity.ok(publishedPolicy); + } + + @PostMapping("/{draftId}/comments") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") + @Operation( + summary = "Add review comment to draft", + description = "Adds a review comment to a policy draft for collaboration and feedback purposes." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Comment added successfully"), + @ApiResponse(responseCode = "400", description = "Invalid comment data"), + @ApiResponse(responseCode = "404", description = "Policy draft not found"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to comment on draft") + }) + public ResponseEntity addComment( + @Parameter(description = "Draft ID") @PathVariable UUID draftId, + @Parameter(description = "Review comment") @RequestParam String comment) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + String userRole = "REVIEWER"; // Simplified - would get actual roles from security context + + log.debug("Adding comment to policy draft: {} by user: {}", draftId, currentUserId); + + PolicyDraftOutputDTO updatedDraft = policyService.addReviewComment(draftId, comment, currentUserId, userRole); + + return ResponseEntity.ok(updatedDraft); + } + + // ========== SPECIALIZED QUERY ENDPOINTS ========== + + @GetMapping("/my-drafts") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") + @Operation( + summary = "Get drafts requiring my attention", + description = "Retrieves drafts that require attention from the current user (created by them or they are a stakeholder)." + ) + public ResponseEntity> getMyDrafts( + @PageableDefault(size = 20, sort = "updatedAt", direction = Sort.Direction.DESC) Pageable pageable) { + + UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); + log.debug("Retrieving drafts requiring attention for user: {}", currentUserId); + + Page drafts = policyService.getDraftsRequiringAttention(currentUserId, pageable); + + return ResponseEntity.ok(drafts); + } + + @GetMapping("/pending-review") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Get drafts pending review", + description = "Retrieves all drafts that are currently pending review (SUBMITTED or UNDER_REVIEW status)." + ) + public ResponseEntity> getDraftsPendingReview( + @PageableDefault(size = 20, sort = "submittedAt", direction = Sort.Direction.ASC) Pageable pageable) { + + log.debug("Retrieving drafts pending review"); + + Page drafts = policyService.getDraftsPendingReview(pageable); + + return ResponseEntity.ok(drafts); + } + + @GetMapping("/overdue") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Get overdue drafts", + description = "Retrieves drafts that have passed their target implementation date but haven't been published yet." + ) + public ResponseEntity> getOverdueDrafts() { + + log.debug("Retrieving overdue drafts"); + + List overdueDrafts = policyService.getOverdueDrafts(); + + return ResponseEntity.ok(overdueDrafts); + } + + // ========== METADATA ENDPOINTS ========== + + @GetMapping("/categories") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") + @Operation( + summary = "Get available draft categories", + description = "Retrieves list of all categories used in policy drafts for filtering and organization." + ) + public ResponseEntity> getDraftCategories() { + + log.debug("Retrieving draft categories"); + + List categories = policyService.getDraftCategories(); + + return ResponseEntity.ok(categories); + } + + @GetMapping("/tags") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") + @Operation( + summary = "Get available draft tags", + description = "Retrieves list of all tags used in policy drafts for filtering and organization." + ) + public ResponseEntity> getDraftTags() { + + log.debug("Retrieving draft tags"); + + List tags = policyService.getDraftTags(); + + return ResponseEntity.ok(tags); + } + + @GetMapping("/statistics") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") + @Operation( + summary = "Get draft statistics", + description = "Retrieves statistical information about policy drafts grouped by status for dashboard displays." + ) + public ResponseEntity> getDraftStatistics() { + + log.debug("Retrieving draft statistics"); + + List statistics = policyService.getDraftStatistics(); + + return ResponseEntity.ok(statistics); + } } \ No newline at end of file diff --git a/src/main/java/com/dalab/policyengine/web/rest/PolicyEvaluationController.java b/src/main/java/com/dalab/policyengine/web/rest/PolicyEvaluationController.java index f1772219efbaecf97adf2cd21f1bb5758bdfc5be..a590948110b7a90f1c995b9b71d84d85f130334e 100644 --- a/src/main/java/com/dalab/policyengine/web/rest/PolicyEvaluationController.java +++ b/src/main/java/com/dalab/policyengine/web/rest/PolicyEvaluationController.java @@ -1,81 +1,81 @@ -package com.dalab.policyengine.web.rest; - -import java.net.URI; -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -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.servlet.support.ServletUriComponentsBuilder; - -import com.dalab.common.security.SecurityUtils; -import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; -import com.dalab.policyengine.dto.PolicyEvaluationRequestDTO; -import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; -import com.dalab.policyengine.service.IPolicyEvaluationService; - -import jakarta.validation.Valid; - -@RestController -@RequestMapping("/api/v1/policyengine") -public class PolicyEvaluationController { - - private static final Logger log = LoggerFactory.getLogger(PolicyEvaluationController.class); - - private final IPolicyEvaluationService policyEvaluationService; - - @Autowired - public PolicyEvaluationController(IPolicyEvaluationService policyEvaluationService) { - this.policyEvaluationService = policyEvaluationService; - } - - @PostMapping("/{policyId}/evaluations") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_SYSTEM')") - public ResponseEntity triggerPolicyEvaluation( - @PathVariable UUID policyId, - @Valid @RequestBody PolicyEvaluationRequestDTO evaluationRequest) { - log.info("REST request to trigger evaluation for Policy: {}", policyId); - - UUID triggeredByUserId = SecurityUtils.getAuthenticatedUserId(); // Can be null if system-triggered via Kafka - PolicyEvaluationOutputDTO evaluation = policyEvaluationService.triggerPolicyEvaluation(policyId, evaluationRequest, triggeredByUserId); - - URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/{evaluationId}") - .buildAndExpand(evaluation.getId()) - .toUri(); - - return ResponseEntity.created(location).body(evaluation); - } - - @GetMapping("/evaluations") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_AUDITOR')") - public ResponseEntity> getPolicyEvaluations( - @PageableDefault(size = 50, sort = "evaluatedAt") Pageable pageable, - @RequestParam(required = false) UUID policyId, - @RequestParam(required = false) String targetAssetId, - @RequestParam(required = false) String status) { - log.info("REST request to get Policy Evaluations with filters: policyId={}, targetAssetId={}, status={}", policyId, targetAssetId, status); - Page resultPage = policyEvaluationService.getPolicyEvaluations(pageable, policyId, targetAssetId, status); - return ResponseEntity.ok(resultPage); - } - - @GetMapping("/evaluations/{evaluationId}") - @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_AUDITOR')") - public ResponseEntity getPolicyEvaluationById(@PathVariable UUID evaluationId) { - log.info("REST request to get Policy Evaluation by id: {}", evaluationId); - PolicyEvaluationOutputDTO evaluationDTO = policyEvaluationService.getPolicyEvaluationById(evaluationId); - return ResponseEntity.ok(evaluationDTO); - } +package com.dalab.policyengine.web.rest; + +import java.net.URI; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +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.servlet.support.ServletUriComponentsBuilder; + +import com.dalab.common.security.SecurityUtils; +import com.dalab.policyengine.dto.PolicyEvaluationOutputDTO; +import com.dalab.policyengine.dto.PolicyEvaluationRequestDTO; +import com.dalab.policyengine.dto.PolicyEvaluationSummaryDTO; +import com.dalab.policyengine.service.IPolicyEvaluationService; + +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/api/v1/policyengine") +public class PolicyEvaluationController { + + private static final Logger log = LoggerFactory.getLogger(PolicyEvaluationController.class); + + private final IPolicyEvaluationService policyEvaluationService; + + @Autowired + public PolicyEvaluationController(IPolicyEvaluationService policyEvaluationService) { + this.policyEvaluationService = policyEvaluationService; + } + + @PostMapping("/{policyId}/evaluations") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_SYSTEM')") + public ResponseEntity triggerPolicyEvaluation( + @PathVariable UUID policyId, + @Valid @RequestBody PolicyEvaluationRequestDTO evaluationRequest) { + log.info("REST request to trigger evaluation for Policy: {}", policyId); + + UUID triggeredByUserId = SecurityUtils.getAuthenticatedUserId(); // Can be null if system-triggered via Kafka + PolicyEvaluationOutputDTO evaluation = policyEvaluationService.triggerPolicyEvaluation(policyId, evaluationRequest, triggeredByUserId); + + URI location = ServletUriComponentsBuilder.fromCurrentRequest() + .path("/{evaluationId}") + .buildAndExpand(evaluation.getId()) + .toUri(); + + return ResponseEntity.created(location).body(evaluation); + } + + @GetMapping("/evaluations") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_AUDITOR')") + public ResponseEntity> getPolicyEvaluations( + @PageableDefault(size = 50, sort = "evaluatedAt") Pageable pageable, + @RequestParam(required = false) UUID policyId, + @RequestParam(required = false) String targetAssetId, + @RequestParam(required = false) String status) { + log.info("REST request to get Policy Evaluations with filters: policyId={}, targetAssetId={}, status={}", policyId, targetAssetId, status); + Page resultPage = policyEvaluationService.getPolicyEvaluations(pageable, policyId, targetAssetId, status); + return ResponseEntity.ok(resultPage); + } + + @GetMapping("/evaluations/{evaluationId}") + @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_AUDITOR')") + public ResponseEntity getPolicyEvaluationById(@PathVariable UUID evaluationId) { + log.info("REST request to get Policy Evaluation by id: {}", evaluationId); + PolicyEvaluationOutputDTO evaluationDTO = policyEvaluationService.getPolicyEvaluationById(evaluationId); + return ResponseEntity.ok(evaluationDTO); + } } \ No newline at end of file diff --git a/src/main/kotlin/com/dalab/policyengine/DaPolicyEngineApplication.kt b/src/main/kotlin/com/dalab/policyengine/DaPolicyEngineApplication.kt index e56d605c7ea8f0473bdc03c7dfe82b12c1384583..8fd3dacbc31c695b95ee325918f1b5e0dbe1b018 100644 --- a/src/main/kotlin/com/dalab/policyengine/DaPolicyEngineApplication.kt +++ b/src/main/kotlin/com/dalab/policyengine/DaPolicyEngineApplication.kt @@ -1,11 +1,11 @@ -package com.dalab.policyengine; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.runApplication; - -@SpringBootApplication -class DaPolicyEngineApplication - -fun main(args: Array) { - runApplication(*args) +package com.dalab.policyengine; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.runApplication; + +@SpringBootApplication +class DaPolicyEngineApplication + +fun main(args: Array) { + runApplication(*args) } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 28dfbb0be9f5d661181085f53d1a7f23c5aba478..b2624071daa9ac44f3bee481fcbf977b3fe38eeb 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,116 +1,116 @@ -# DALab Policy Engine Service Configuration -# Updated to use the DALab infrastructure - -spring.application.name=da-policyengine - -# Server Configuration -server.port=8080 -server.servlet.context-path=/api/v1/policyengine - -# Database Configuration - using infrastructure PostgreSQL -spring.datasource.url=jdbc:postgresql://localhost:5432/da_policyengine -spring.datasource.username=da_policyengine_user -spring.datasource.password=da_policyengine_pass -spring.datasource.driver-class-name=org.postgresql.Driver - -# Connection Pool Configuration -spring.datasource.hikari.maximum-pool-size=10 -spring.datasource.hikari.minimum-idle=2 -spring.datasource.hikari.connection-timeout=30000 -spring.datasource.hikari.idle-timeout=600000 -spring.datasource.hikari.max-lifetime=1800000 - -# JPA Configuration -spring.jpa.hibernate.ddl-auto=update -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.properties.hibernate.default_schema=da_policyengine_schema -spring.jpa.properties.hibernate.jdbc.time_zone=UTC -spring.jpa.properties.hibernate.format_sql=true -spring.jpa.show-sql=false - -# Kafka Configuration - using infrastructure Kafka -spring.kafka.bootstrap-servers=localhost:9092 -spring.kafka.consumer.group-id=${spring.application.name} -spring.kafka.consumer.auto-offset-reset=earliest -spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer -spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer -spring.kafka.consumer.properties.spring.json.trusted.packages=com.dalab.* -spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer -spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer -spring.kafka.producer.retries=3 -spring.kafka.producer.acks=all - -# Security Configuration - using infrastructure Keycloak -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8180/realms/dalab -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8180/realms/dalab/protocol/openid-connect/certs - -# Management/Actuator Configuration -management.endpoints.web.exposure.include=health,info,metrics,prometheus -management.endpoint.health.show-details=when-authorized -management.metrics.export.prometheus.enabled=true - -# Logging Configuration -logging.level.com.dalab=DEBUG -logging.level.org.springframework.kafka=INFO -logging.level.org.springframework.security=INFO -logging.level.org.hibernate.SQL=DEBUG -logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n - -# DALab Specific Configuration -dalab.service.name=Policy Engine Service -dalab.service.version=1.0.0 -dalab.service.description=Policy evaluation and enforcement engine - -# Common entities database connection (for da-protos entities) -dalab.common-db.url=jdbc:postgresql://localhost:5432/dalab_common -dalab.common-db.username=da_policyengine_user -dalab.common-db.password=da_policyengine_pass - -# Kafka topics -dalab.kafka.topics.asset-changes=dalab.assets.changes -dalab.kafka.topics.policy-actions=dalab.policies.actions -dalab.kafka.topics.policy-evaluations=dalab.policies.evaluations - -# Custom application properties for Kafka topics -app.kafka.topic.asset-change-event=dalab.assets.changes -app.kafka.topic.policy-action-event=dalab.policies.actions - -# Easy Rules Configuration -easy.rules.skipOnFirstAppliedRule=false -easy.rules.skipOnFirstFailedRule=false -easy.rules.rulePriorityThreshold=1 - -# CORS Configuration -dalab.security.allowed-origins=http://localhost:3000,http://localhost:4200 - -# JWT Claims Configuration -dalab.security.jwt.claims.user-id=sub -dalab.security.jwt.claims.username=preferred_username -dalab.security.jwt.claims.roles=realm_access.roles - -# OpenAPI Documentation -springdoc.api-docs.path=/api/v1/policyengine/api-docs -springdoc.swagger-ui.path=/swagger-ui.html -springdoc.swagger-ui.operationsSorter=alpha -springdoc.swagger-ui.tagsSorter=alpha -springdoc.show-actuator=true - -# Event Center Configuration -dalab.event-center.enabled=true -dalab.event-center.max-events-per-stream=100 -dalab.event-center.analytics-cache-duration=300 -dalab.event-center.notification-retry-attempts=3 -dalab.event-center.notification-timeout-ms=5000 - -# Event Center Kafka Topics -dalab.kafka.topics.event-stream=dalab.events.stream -dalab.kafka.topics.event-notifications=dalab.events.notifications -dalab.kafka.topics.event-analytics=dalab.events.analytics - -# WebSocket Configuration for Event Streaming -spring.websocket.enabled=true -dalab.websocket.event-stream-endpoint=/ws/events -dalab.websocket.allowed-origins=http://localhost:3000,http://localhost:4200 - -# Event Center Source Services +# DALab Policy Engine Service Configuration +# Updated to use the DALab infrastructure + +spring.application.name=da-policyengine + +# Server Configuration +server.port=8080 +server.servlet.context-path=/api/v1/policyengine + +# Database Configuration - using infrastructure PostgreSQL +spring.datasource.url=jdbc:postgresql://localhost:5432/da_policyengine +spring.datasource.username=da_policyengine_user +spring.datasource.password=da_policyengine_pass +spring.datasource.driver-class-name=org.postgresql.Driver + +# Connection Pool Configuration +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=2 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.idle-timeout=600000 +spring.datasource.hikari.max-lifetime=1800000 + +# JPA Configuration +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.properties.hibernate.default_schema=da_policyengine_schema +spring.jpa.properties.hibernate.jdbc.time_zone=UTC +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.show-sql=false + +# Kafka Configuration - using infrastructure Kafka +spring.kafka.bootstrap-servers=localhost:9092 +spring.kafka.consumer.group-id=${spring.application.name} +spring.kafka.consumer.auto-offset-reset=earliest +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.properties.spring.json.trusted.packages=com.dalab.* +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.retries=3 +spring.kafka.producer.acks=all + +# Security Configuration - using infrastructure Keycloak +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8180/realms/dalab +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8180/realms/dalab/protocol/openid-connect/certs + +# Management/Actuator Configuration +management.endpoints.web.exposure.include=health,info,metrics,prometheus +management.endpoint.health.show-details=when-authorized +management.metrics.export.prometheus.enabled=true + +# Logging Configuration +logging.level.com.dalab=DEBUG +logging.level.org.springframework.kafka=INFO +logging.level.org.springframework.security=INFO +logging.level.org.hibernate.SQL=DEBUG +logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n + +# DALab Specific Configuration +dalab.service.name=Policy Engine Service +dalab.service.version=1.0.0 +dalab.service.description=Policy evaluation and enforcement engine + +# Common entities database connection (for da-protos entities) +dalab.common-db.url=jdbc:postgresql://localhost:5432/dalab_common +dalab.common-db.username=da_policyengine_user +dalab.common-db.password=da_policyengine_pass + +# Kafka topics +dalab.kafka.topics.asset-changes=dalab.assets.changes +dalab.kafka.topics.policy-actions=dalab.policies.actions +dalab.kafka.topics.policy-evaluations=dalab.policies.evaluations + +# Custom application properties for Kafka topics +app.kafka.topic.asset-change-event=dalab.assets.changes +app.kafka.topic.policy-action-event=dalab.policies.actions + +# Easy Rules Configuration +easy.rules.skipOnFirstAppliedRule=false +easy.rules.skipOnFirstFailedRule=false +easy.rules.rulePriorityThreshold=1 + +# CORS Configuration +dalab.security.allowed-origins=http://localhost:3000,http://localhost:4200 + +# JWT Claims Configuration +dalab.security.jwt.claims.user-id=sub +dalab.security.jwt.claims.username=preferred_username +dalab.security.jwt.claims.roles=realm_access.roles + +# OpenAPI Documentation +springdoc.api-docs.path=/api/v1/policyengine/api-docs +springdoc.swagger-ui.path=/swagger-ui.html +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.tagsSorter=alpha +springdoc.show-actuator=true + +# Event Center Configuration +dalab.event-center.enabled=true +dalab.event-center.max-events-per-stream=100 +dalab.event-center.analytics-cache-duration=300 +dalab.event-center.notification-retry-attempts=3 +dalab.event-center.notification-timeout-ms=5000 + +# Event Center Kafka Topics +dalab.kafka.topics.event-stream=dalab.events.stream +dalab.kafka.topics.event-notifications=dalab.events.notifications +dalab.kafka.topics.event-analytics=dalab.events.analytics + +# WebSocket Configuration for Event Streaming +spring.websocket.enabled=true +dalab.websocket.event-stream-endpoint=/ws/events +dalab.websocket.allowed-origins=http://localhost:3000,http://localhost:4200 + +# Event Center Source Services dalab.event-center.source-services=da-catalog,da-discovery,da-compliance,da-policyengine,da-reporting,da-governance,da-lifecycle,da-classification,da-lineage \ No newline at end of file diff --git a/src/test/java/com/dalab/policyengine/controller/PolicyControllerTest.java b/src/test/java/com/dalab/policyengine/controller/PolicyControllerTest.java index 0bb56a925a0ed69a1890ad816bf2ddaef7105c88..ee60b74d91d403a5d9bf05d906cfaf63696f6dec 100644 --- a/src/test/java/com/dalab/policyengine/controller/PolicyControllerTest.java +++ b/src/test/java/com/dalab/policyengine/controller/PolicyControllerTest.java @@ -1,496 +1,496 @@ -package com.dalab.policyengine.controller; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import java.time.LocalDateTime; -import java.util.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; - -import com.dalab.policyengine.dto.*; -import com.dalab.policyengine.model.Policy; -import com.dalab.policyengine.model.PolicyDraft; -import com.dalab.policyengine.model.PolicyStatus; -import com.dalab.policyengine.service.IPolicyService; -import com.dalab.policyengine.service.IPolicyEvaluationService; -import com.dalab.policyengine.service.IPolicyDraftService; -import com.dalab.policyengine.service.IEventSubscriptionService; -import com.fasterxml.jackson.databind.ObjectMapper; - -@WebMvcTest(PolicyController.class) -@ExtendWith(MockitoExtension.class) -@ActiveProfiles("test") -class PolicyControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private IPolicyService policyService; - - @MockBean - private IPolicyEvaluationService policyEvaluationService; - - @MockBean - private IPolicyDraftService policyDraftService; - - @MockBean - private IEventSubscriptionService eventSubscriptionService; - - @Autowired - private ObjectMapper objectMapper; - - private Policy testPolicy; - private PolicyDTO testPolicyDTO; - private PolicyDraft testPolicyDraft; - private PolicyDraftDTO testPolicyDraftDTO; - private PolicyEvaluationRequestDTO testEvaluationRequest; - private PolicyImpactRequestDTO testImpactRequest; - private PolicyImpactResponseDTO testImpactResponse; - - @BeforeEach - void setUp() { - // Create test policy - testPolicy = new Policy(); - testPolicy.setId(UUID.randomUUID()); - testPolicy.setName("Test Policy"); - testPolicy.setDescription("Test policy for compliance"); - testPolicy.setRulesContent("when asset.type == 'PII' then tag('SENSITIVE')"); - testPolicy.setStatus(PolicyStatus.ENABLED); - testPolicy.setCreatedAt(LocalDateTime.now()); - testPolicy.setUpdatedAt(LocalDateTime.now()); - - // Create test policy DTO - testPolicyDTO = PolicyDTO.builder() - .id(testPolicy.getId()) - .name("Test Policy") - .description("Test policy for compliance") - .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") - .status("ENABLED") - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - // Create test policy draft - testPolicyDraft = new PolicyDraft(); - testPolicyDraft.setId(UUID.randomUUID()); - testPolicyDraft.setName("Test Draft Policy"); - testPolicyDraft.setDescription("Draft policy for testing"); - testPolicyDraft.setRulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')"); - testPolicyDraft.setStatus("DRAFT"); - testPolicyDraft.setCreatedAt(LocalDateTime.now()); - - // Create test policy draft DTO - testPolicyDraftDTO = PolicyDraftDTO.builder() - .id(testPolicyDraft.getId()) - .name("Test Draft Policy") - .description("Draft policy for testing") - .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") - .status("DRAFT") - .createdAt(LocalDateTime.now()) - .build(); - - // Create test evaluation request - testEvaluationRequest = PolicyEvaluationRequestDTO.builder() - .policyId(testPolicy.getId()) - .assetId(UUID.randomUUID()) - .evaluationContext(Map.of("asset.type", "PII")) - .build(); - - // Create test impact request - testImpactRequest = PolicyImpactRequestDTO.builder() - .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") - .analysisType("FULL") - .includePerformanceEstimate(true) - .includeCostImpact(true) - .includeComplianceImpact(true) - .build(); - - // Create test impact response - testImpactResponse = PolicyImpactResponseDTO.builder() - .analysisId("impact-12345678") - .analysisType("FULL") - .build(); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void createPolicy_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() - .name("New Test Policy") - .description("New test policy for compliance") - .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") - .build(); - - when(policyService.createPolicy(any(PolicyCreateRequestDTO.class))).thenReturn(testPolicyDTO); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) - .andExpect(jsonPath("$.name").value("Test Policy")) - .andExpect(jsonPath("$.status").value("ENABLED")); - } - - @Test - @WithMockUser(authorities = "ROLE_USER") - void createPolicy_AsUser_ShouldBeForbidden() throws Exception { - // Given - PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() - .name("New Test Policy") - .description("New test policy for compliance") - .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") - .build(); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isForbidden()); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void getAllPolicies_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - List policyList = Arrays.asList(testPolicyDTO); - Page policyPage = new PageImpl<>(policyList, PageRequest.of(0, 20), 1); - - when(policyService.getAllPolicies(any(Pageable.class))).thenReturn(policyPage); - - // When & Then - mockMvc.perform(get("/api/v1/policyengine/policies") - .param("page", "0") - .param("size", "20")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.content[0].id").value(testPolicy.getId().toString())) - .andExpect(jsonPath("$.content[0].name").value("Test Policy")) - .andExpect(jsonPath("$.totalElements").value(1)); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void getPolicyById_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - when(policyService.getPolicyById(testPolicy.getId())).thenReturn(Optional.of(testPolicyDTO)); - - // When & Then - mockMvc.perform(get("/api/v1/policyengine/policies/{policyId}", testPolicy.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) - .andExpect(jsonPath("$.name").value("Test Policy")) - .andExpect(jsonPath("$.status").value("ENABLED")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void getPolicyById_PolicyNotFound_ShouldReturnNotFound() throws Exception { - // Given - UUID nonExistentId = UUID.randomUUID(); - when(policyService.getPolicyById(nonExistentId)).thenReturn(Optional.empty()); - - // When & Then - mockMvc.perform(get("/api/v1/policyengine/policies/{policyId}", nonExistentId)) - .andExpect(status().isNotFound()); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void updatePolicy_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - PolicyUpdateRequestDTO request = PolicyUpdateRequestDTO.builder() - .name("Updated Test Policy") - .description("Updated test policy for compliance") - .rulesContent("when asset.type == 'PII' then tag('SENSITIVE') and notify('admin')") - .status("ENABLED") - .build(); - - PolicyDTO updatedPolicy = PolicyDTO.builder() - .id(testPolicy.getId()) - .name("Updated Test Policy") - .description("Updated test policy for compliance") - .rulesContent("when asset.type == 'PII' then tag('SENSITIVE') and notify('admin')") - .status("ENABLED") - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - when(policyService.updatePolicy(eq(testPolicy.getId()), any(PolicyUpdateRequestDTO.class))) - .thenReturn(updatedPolicy); - - // When & Then - mockMvc.perform(put("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) - .andExpect(jsonPath("$.name").value("Updated Test Policy")) - .andExpect(jsonPath("$.status").value("ENABLED")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void deletePolicy_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - doNothing().when(policyService).deletePolicy(testPolicy.getId()); - - // When & Then - mockMvc.perform(delete("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) - .with(csrf())) - .andExpect(status().isNoContent()); - } - - @Test - @WithMockUser(authorities = "ROLE_USER") - void deletePolicy_AsUser_ShouldBeForbidden() throws Exception { - // When & Then - mockMvc.perform(delete("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) - .with(csrf())) - .andExpect(status().isForbidden()); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void evaluatePolicy_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - PolicyEvaluationResponseDTO evaluationResponse = PolicyEvaluationResponseDTO.builder() - .policyId(testPolicy.getId()) - .assetId(testEvaluationRequest.getAssetId()) - .evaluationResult("PASS") - .actions(Arrays.asList("tag('SENSITIVE')")) - .evaluationTimestamp(LocalDateTime.now()) - .build(); - - when(policyEvaluationService.evaluatePolicy(any(PolicyEvaluationRequestDTO.class))) - .thenReturn(evaluationResponse); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies/evaluate") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(testEvaluationRequest))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.policyId").value(testPolicy.getId().toString())) - .andExpect(jsonPath("$.evaluationResult").value("PASS")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void analyzePolicyImpact_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - when(policyService.analyzePolicy(any(PolicyImpactRequestDTO.class))) - .thenReturn(testImpactResponse); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies/{policyId}/impact-preview", testPolicy.getId()) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(testImpactRequest))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.analysisId").value("impact-12345678")) - .andExpect(jsonPath("$.analysisType").value("FULL")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void createPolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - PolicyDraftCreateRequestDTO request = PolicyDraftCreateRequestDTO.builder() - .name("New Draft Policy") - .description("New draft policy for testing") - .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") - .build(); - - when(policyDraftService.createPolicyDraft(any(PolicyDraftCreateRequestDTO.class))) - .thenReturn(testPolicyDraftDTO); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies/drafts") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) - .andExpect(jsonPath("$.name").value("Test Draft Policy")) - .andExpect(jsonPath("$.status").value("DRAFT")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void getAllPolicyDrafts_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - List draftList = Arrays.asList(testPolicyDraftDTO); - Page draftPage = new PageImpl<>(draftList, PageRequest.of(0, 20), 1); - - when(policyDraftService.getAllPolicyDrafts(any(Pageable.class))).thenReturn(draftPage); - - // When & Then - mockMvc.perform(get("/api/v1/policyengine/policies/drafts") - .param("page", "0") - .param("size", "20")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.content[0].id").value(testPolicyDraft.getId().toString())) - .andExpect(jsonPath("$.content[0].name").value("Test Draft Policy")) - .andExpect(jsonPath("$.totalElements").value(1)); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void getPolicyDraftById_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - when(policyDraftService.getPolicyDraftById(testPolicyDraft.getId())) - .thenReturn(Optional.of(testPolicyDraftDTO)); - - // When & Then - mockMvc.perform(get("/api/v1/policyengine/policies/drafts/{draftId}", testPolicyDraft.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) - .andExpect(jsonPath("$.name").value("Test Draft Policy")) - .andExpect(jsonPath("$.status").value("DRAFT")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void updatePolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - PolicyDraftUpdateRequestDTO request = PolicyDraftUpdateRequestDTO.builder() - .name("Updated Draft Policy") - .description("Updated draft policy for testing") - .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE') and encrypt()") - .build(); - - PolicyDraftDTO updatedDraft = PolicyDraftDTO.builder() - .id(testPolicyDraft.getId()) - .name("Updated Draft Policy") - .description("Updated draft policy for testing") - .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE') and encrypt()") - .status("DRAFT") - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - when(policyDraftService.updatePolicyDraft(eq(testPolicyDraft.getId()), any(PolicyDraftUpdateRequestDTO.class))) - .thenReturn(updatedDraft); - - // When & Then - mockMvc.perform(put("/api/v1/policyengine/policies/drafts/{draftId}", testPolicyDraft.getId()) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) - .andExpect(jsonPath("$.name").value("Updated Draft Policy")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void approvePolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { - // Given - PolicyDraftApprovalRequestDTO request = PolicyDraftApprovalRequestDTO.builder() - .approved(true) - .comments("Approved for production use") - .build(); - - PolicyDTO approvedPolicy = PolicyDTO.builder() - .id(UUID.randomUUID()) - .name("Approved Policy") - .description("Policy approved from draft") - .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") - .status("ENABLED") - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - when(policyDraftService.approvePolicyDraft(eq(testPolicyDraft.getId()), any(PolicyDraftApprovalRequestDTO.class))) - .thenReturn(approvedPolicy); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies/drafts/{draftId}/approve", testPolicyDraft.getId()) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.name").value("Approved Policy")) - .andExpect(jsonPath("$.status").value("ENABLED")); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void createPolicy_InvalidRequest_ShouldReturnBadRequest() throws Exception { - // Given - PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() - .name("") // Invalid: empty name - .description("Test policy") - .rulesContent("invalid rule syntax") - .build(); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void evaluatePolicy_InvalidRequest_ShouldReturnBadRequest() throws Exception { - // Given - PolicyEvaluationRequestDTO request = PolicyEvaluationRequestDTO.builder() - .policyId(null) // Invalid: null policy ID - .assetId(UUID.randomUUID()) - .evaluationContext(Map.of("asset.type", "PII")) - .build(); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies/evaluate") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); - } - - @Test - @WithMockUser(authorities = "ROLE_DATA_ENGINEER") - void analyzePolicyImpact_InvalidRequest_ShouldReturnBadRequest() throws Exception { - // Given - PolicyImpactRequestDTO request = PolicyImpactRequestDTO.builder() - .rulesContent("") // Invalid: empty rules content - .analysisType("INVALID_TYPE") // Invalid: unknown analysis type - .build(); - - // When & Then - mockMvc.perform(post("/api/v1/policyengine/policies/{policyId}/impact-preview", testPolicy.getId()) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); - } +package com.dalab.policyengine.controller; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDateTime; +import java.util.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import com.dalab.policyengine.dto.*; +import com.dalab.policyengine.model.Policy; +import com.dalab.policyengine.model.PolicyDraft; +import com.dalab.policyengine.model.PolicyStatus; +import com.dalab.policyengine.service.IPolicyService; +import com.dalab.policyengine.service.IPolicyEvaluationService; +import com.dalab.policyengine.service.IPolicyDraftService; +import com.dalab.policyengine.service.IEventSubscriptionService; +import com.fasterxml.jackson.databind.ObjectMapper; + +@WebMvcTest(PolicyController.class) +@ExtendWith(MockitoExtension.class) +@ActiveProfiles("test") +class PolicyControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private IPolicyService policyService; + + @MockBean + private IPolicyEvaluationService policyEvaluationService; + + @MockBean + private IPolicyDraftService policyDraftService; + + @MockBean + private IEventSubscriptionService eventSubscriptionService; + + @Autowired + private ObjectMapper objectMapper; + + private Policy testPolicy; + private PolicyDTO testPolicyDTO; + private PolicyDraft testPolicyDraft; + private PolicyDraftDTO testPolicyDraftDTO; + private PolicyEvaluationRequestDTO testEvaluationRequest; + private PolicyImpactRequestDTO testImpactRequest; + private PolicyImpactResponseDTO testImpactResponse; + + @BeforeEach + void setUp() { + // Create test policy + testPolicy = new Policy(); + testPolicy.setId(UUID.randomUUID()); + testPolicy.setName("Test Policy"); + testPolicy.setDescription("Test policy for compliance"); + testPolicy.setRulesContent("when asset.type == 'PII' then tag('SENSITIVE')"); + testPolicy.setStatus(PolicyStatus.ENABLED); + testPolicy.setCreatedAt(LocalDateTime.now()); + testPolicy.setUpdatedAt(LocalDateTime.now()); + + // Create test policy DTO + testPolicyDTO = PolicyDTO.builder() + .id(testPolicy.getId()) + .name("Test Policy") + .description("Test policy for compliance") + .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") + .status("ENABLED") + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + + // Create test policy draft + testPolicyDraft = new PolicyDraft(); + testPolicyDraft.setId(UUID.randomUUID()); + testPolicyDraft.setName("Test Draft Policy"); + testPolicyDraft.setDescription("Draft policy for testing"); + testPolicyDraft.setRulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')"); + testPolicyDraft.setStatus("DRAFT"); + testPolicyDraft.setCreatedAt(LocalDateTime.now()); + + // Create test policy draft DTO + testPolicyDraftDTO = PolicyDraftDTO.builder() + .id(testPolicyDraft.getId()) + .name("Test Draft Policy") + .description("Draft policy for testing") + .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") + .status("DRAFT") + .createdAt(LocalDateTime.now()) + .build(); + + // Create test evaluation request + testEvaluationRequest = PolicyEvaluationRequestDTO.builder() + .policyId(testPolicy.getId()) + .assetId(UUID.randomUUID()) + .evaluationContext(Map.of("asset.type", "PII")) + .build(); + + // Create test impact request + testImpactRequest = PolicyImpactRequestDTO.builder() + .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") + .analysisType("FULL") + .includePerformanceEstimate(true) + .includeCostImpact(true) + .includeComplianceImpact(true) + .build(); + + // Create test impact response + testImpactResponse = PolicyImpactResponseDTO.builder() + .analysisId("impact-12345678") + .analysisType("FULL") + .build(); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void createPolicy_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() + .name("New Test Policy") + .description("New test policy for compliance") + .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") + .build(); + + when(policyService.createPolicy(any(PolicyCreateRequestDTO.class))).thenReturn(testPolicyDTO); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) + .andExpect(jsonPath("$.name").value("Test Policy")) + .andExpect(jsonPath("$.status").value("ENABLED")); + } + + @Test + @WithMockUser(authorities = "ROLE_USER") + void createPolicy_AsUser_ShouldBeForbidden() throws Exception { + // Given + PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() + .name("New Test Policy") + .description("New test policy for compliance") + .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") + .build(); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void getAllPolicies_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + List policyList = Arrays.asList(testPolicyDTO); + Page policyPage = new PageImpl<>(policyList, PageRequest.of(0, 20), 1); + + when(policyService.getAllPolicies(any(Pageable.class))).thenReturn(policyPage); + + // When & Then + mockMvc.perform(get("/api/v1/policyengine/policies") + .param("page", "0") + .param("size", "20")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content[0].id").value(testPolicy.getId().toString())) + .andExpect(jsonPath("$.content[0].name").value("Test Policy")) + .andExpect(jsonPath("$.totalElements").value(1)); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void getPolicyById_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + when(policyService.getPolicyById(testPolicy.getId())).thenReturn(Optional.of(testPolicyDTO)); + + // When & Then + mockMvc.perform(get("/api/v1/policyengine/policies/{policyId}", testPolicy.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) + .andExpect(jsonPath("$.name").value("Test Policy")) + .andExpect(jsonPath("$.status").value("ENABLED")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void getPolicyById_PolicyNotFound_ShouldReturnNotFound() throws Exception { + // Given + UUID nonExistentId = UUID.randomUUID(); + when(policyService.getPolicyById(nonExistentId)).thenReturn(Optional.empty()); + + // When & Then + mockMvc.perform(get("/api/v1/policyengine/policies/{policyId}", nonExistentId)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void updatePolicy_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + PolicyUpdateRequestDTO request = PolicyUpdateRequestDTO.builder() + .name("Updated Test Policy") + .description("Updated test policy for compliance") + .rulesContent("when asset.type == 'PII' then tag('SENSITIVE') and notify('admin')") + .status("ENABLED") + .build(); + + PolicyDTO updatedPolicy = PolicyDTO.builder() + .id(testPolicy.getId()) + .name("Updated Test Policy") + .description("Updated test policy for compliance") + .rulesContent("when asset.type == 'PII' then tag('SENSITIVE') and notify('admin')") + .status("ENABLED") + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + + when(policyService.updatePolicy(eq(testPolicy.getId()), any(PolicyUpdateRequestDTO.class))) + .thenReturn(updatedPolicy); + + // When & Then + mockMvc.perform(put("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) + .andExpect(jsonPath("$.name").value("Updated Test Policy")) + .andExpect(jsonPath("$.status").value("ENABLED")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void deletePolicy_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + doNothing().when(policyService).deletePolicy(testPolicy.getId()); + + // When & Then + mockMvc.perform(delete("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) + .with(csrf())) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(authorities = "ROLE_USER") + void deletePolicy_AsUser_ShouldBeForbidden() throws Exception { + // When & Then + mockMvc.perform(delete("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) + .with(csrf())) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void evaluatePolicy_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + PolicyEvaluationResponseDTO evaluationResponse = PolicyEvaluationResponseDTO.builder() + .policyId(testPolicy.getId()) + .assetId(testEvaluationRequest.getAssetId()) + .evaluationResult("PASS") + .actions(Arrays.asList("tag('SENSITIVE')")) + .evaluationTimestamp(LocalDateTime.now()) + .build(); + + when(policyEvaluationService.evaluatePolicy(any(PolicyEvaluationRequestDTO.class))) + .thenReturn(evaluationResponse); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies/evaluate") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testEvaluationRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.policyId").value(testPolicy.getId().toString())) + .andExpect(jsonPath("$.evaluationResult").value("PASS")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void analyzePolicyImpact_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + when(policyService.analyzePolicy(any(PolicyImpactRequestDTO.class))) + .thenReturn(testImpactResponse); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies/{policyId}/impact-preview", testPolicy.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testImpactRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.analysisId").value("impact-12345678")) + .andExpect(jsonPath("$.analysisType").value("FULL")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void createPolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + PolicyDraftCreateRequestDTO request = PolicyDraftCreateRequestDTO.builder() + .name("New Draft Policy") + .description("New draft policy for testing") + .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") + .build(); + + when(policyDraftService.createPolicyDraft(any(PolicyDraftCreateRequestDTO.class))) + .thenReturn(testPolicyDraftDTO); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies/drafts") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) + .andExpect(jsonPath("$.name").value("Test Draft Policy")) + .andExpect(jsonPath("$.status").value("DRAFT")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void getAllPolicyDrafts_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + List draftList = Arrays.asList(testPolicyDraftDTO); + Page draftPage = new PageImpl<>(draftList, PageRequest.of(0, 20), 1); + + when(policyDraftService.getAllPolicyDrafts(any(Pageable.class))).thenReturn(draftPage); + + // When & Then + mockMvc.perform(get("/api/v1/policyengine/policies/drafts") + .param("page", "0") + .param("size", "20")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content[0].id").value(testPolicyDraft.getId().toString())) + .andExpect(jsonPath("$.content[0].name").value("Test Draft Policy")) + .andExpect(jsonPath("$.totalElements").value(1)); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void getPolicyDraftById_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + when(policyDraftService.getPolicyDraftById(testPolicyDraft.getId())) + .thenReturn(Optional.of(testPolicyDraftDTO)); + + // When & Then + mockMvc.perform(get("/api/v1/policyengine/policies/drafts/{draftId}", testPolicyDraft.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) + .andExpect(jsonPath("$.name").value("Test Draft Policy")) + .andExpect(jsonPath("$.status").value("DRAFT")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void updatePolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + PolicyDraftUpdateRequestDTO request = PolicyDraftUpdateRequestDTO.builder() + .name("Updated Draft Policy") + .description("Updated draft policy for testing") + .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE') and encrypt()") + .build(); + + PolicyDraftDTO updatedDraft = PolicyDraftDTO.builder() + .id(testPolicyDraft.getId()) + .name("Updated Draft Policy") + .description("Updated draft policy for testing") + .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE') and encrypt()") + .status("DRAFT") + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + + when(policyDraftService.updatePolicyDraft(eq(testPolicyDraft.getId()), any(PolicyDraftUpdateRequestDTO.class))) + .thenReturn(updatedDraft); + + // When & Then + mockMvc.perform(put("/api/v1/policyengine/policies/drafts/{draftId}", testPolicyDraft.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) + .andExpect(jsonPath("$.name").value("Updated Draft Policy")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void approvePolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { + // Given + PolicyDraftApprovalRequestDTO request = PolicyDraftApprovalRequestDTO.builder() + .approved(true) + .comments("Approved for production use") + .build(); + + PolicyDTO approvedPolicy = PolicyDTO.builder() + .id(UUID.randomUUID()) + .name("Approved Policy") + .description("Policy approved from draft") + .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") + .status("ENABLED") + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + + when(policyDraftService.approvePolicyDraft(eq(testPolicyDraft.getId()), any(PolicyDraftApprovalRequestDTO.class))) + .thenReturn(approvedPolicy); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies/drafts/{draftId}/approve", testPolicyDraft.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Approved Policy")) + .andExpect(jsonPath("$.status").value("ENABLED")); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void createPolicy_InvalidRequest_ShouldReturnBadRequest() throws Exception { + // Given + PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() + .name("") // Invalid: empty name + .description("Test policy") + .rulesContent("invalid rule syntax") + .build(); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void evaluatePolicy_InvalidRequest_ShouldReturnBadRequest() throws Exception { + // Given + PolicyEvaluationRequestDTO request = PolicyEvaluationRequestDTO.builder() + .policyId(null) // Invalid: null policy ID + .assetId(UUID.randomUUID()) + .evaluationContext(Map.of("asset.type", "PII")) + .build(); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies/evaluate") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(authorities = "ROLE_DATA_ENGINEER") + void analyzePolicyImpact_InvalidRequest_ShouldReturnBadRequest() throws Exception { + // Given + PolicyImpactRequestDTO request = PolicyImpactRequestDTO.builder() + .rulesContent("") // Invalid: empty rules content + .analysisType("INVALID_TYPE") // Invalid: unknown analysis type + .build(); + + // When & Then + mockMvc.perform(post("/api/v1/policyengine/policies/{policyId}/impact-preview", testPolicy.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } } \ No newline at end of file diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 6de2b292965f2da04ca806fef2b0c5cb87cd9c6c..ff88f85aa4da905818572f585de1cec941ef3f24 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -1,90 +1,90 @@ -# Test configuration for da-policyengine integration tests -spring: - application: - name: da-policyengine-test - - # Database configuration (using H2 for tests) - datasource: - type: com.zaxxer.hikari.HikariDataSource - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - username: sa - password: password - hikari: - auto-commit: false - maximum-pool-size: 10 - minimum-idle: 2 - connection-timeout: 30000 - idle-timeout: 600000 - max-lifetime: 1800000 - - jpa: - database-platform: org.hibernate.dialect.H2Dialect - hibernate: - ddl-auto: create-drop - show-sql: false - properties: - hibernate: - format_sql: false - jdbc: - lob: - non_contextual_creation: true - dialect: org.hibernate.dialect.H2Dialect - - h2: - console: - enabled: false - - # Disable Liquibase for tests - liquibase: - enabled: false - - # Disable Docker for tests - docker: - compose: - enabled: false - - # Task execution configuration for tests - task: - execution: - pool: - core-size: 2 - max-size: 4 - queue-capacity: 100 - thread-name-prefix: test-task- - - # Cache configuration for tests - cache: - type: simple - -# Disable Docker for tests -testcontainers: - enabled: false - -# Security configuration for tests -dalab: - security: - jwt: - enabled: false - oauth2: - enabled: false - -# Kafka configuration for tests (disabled) -spring: - kafka: - enabled: false - bootstrap-servers: localhost:9092 - consumer: - group-id: da-policyengine-test - auto-offset-reset: earliest - producer: - key-serializer: org.apache.kafka.common.serialization.StringSerializer - value-serializer: org.apache.kafka.common.serialization.StringSerializer - -# Easy Rules configuration for tests -easy-rules: - rules-engine: - skip-on-first-failed-rule: false - skip-on-first-applied-rule: false - skip-on-first-non-triggered-rule: false +# Test configuration for da-policyengine integration tests +spring: + application: + name: da-policyengine-test + + # Database configuration (using H2 for tests) + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: password + hikari: + auto-commit: false + maximum-pool-size: 10 + minimum-idle: 2 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: create-drop + show-sql: false + properties: + hibernate: + format_sql: false + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.H2Dialect + + h2: + console: + enabled: false + + # Disable Liquibase for tests + liquibase: + enabled: false + + # Disable Docker for tests + docker: + compose: + enabled: false + + # Task execution configuration for tests + task: + execution: + pool: + core-size: 2 + max-size: 4 + queue-capacity: 100 + thread-name-prefix: test-task- + + # Cache configuration for tests + cache: + type: simple + +# Disable Docker for tests +testcontainers: + enabled: false + +# Security configuration for tests +dalab: + security: + jwt: + enabled: false + oauth2: + enabled: false + +# Kafka configuration for tests (disabled) +spring: + kafka: + enabled: false + bootstrap-servers: localhost:9092 + consumer: + group-id: da-policyengine-test + auto-offset-reset: earliest + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + +# Easy Rules configuration for tests +easy-rules: + rules-engine: + skip-on-first-failed-rule: false + skip-on-first-applied-rule: false + skip-on-first-non-triggered-rule: false rule-priority-threshold: 2147483647 \ No newline at end of file