Ajay Yadav commited on
Commit
623efaa
·
1 Parent(s): 0391561

Initial deployment of da-catalog-dev

Browse files
Files changed (45) hide show
  1. Dockerfile +27 -0
  2. README.md +33 -5
  3. build.gradle.kts +12 -0
  4. src/main/docker/Dockerfile +23 -0
  5. src/main/docker/Dockerfile.alpine-jlink +43 -0
  6. src/main/docker/Dockerfile.distroless +29 -0
  7. src/main/docker/Dockerfile.layered +34 -0
  8. src/main/docker/Dockerfile.native +20 -0
  9. src/main/java/com/dalab/catalog/DaCatalogApplication.java +20 -0
  10. src/main/java/com/dalab/catalog/common/ConflictException.java +15 -0
  11. src/main/java/com/dalab/catalog/common/ResourceNotFoundException.java +19 -0
  12. src/main/java/com/dalab/catalog/dto/AssetComplianceStatusDTO.java +413 -0
  13. src/main/java/com/dalab/catalog/dto/AssetInputDTO.java +105 -0
  14. src/main/java/com/dalab/catalog/dto/AssetLabelsPostRequestDTO.java +22 -0
  15. src/main/java/com/dalab/catalog/dto/AssetLabelsResponseDTO.java +21 -0
  16. src/main/java/com/dalab/catalog/dto/AssetOutputDTO.java +150 -0
  17. src/main/java/com/dalab/catalog/dto/AssetPolicyMappingDTO.java +287 -0
  18. src/main/java/com/dalab/catalog/dto/AssetSchemaDTO.java +574 -0
  19. src/main/java/com/dalab/catalog/dto/AssetUsageAnalyticsDTO.java +446 -0
  20. src/main/java/com/dalab/catalog/dto/BusinessMetadataInputDTO.java +409 -0
  21. src/main/java/com/dalab/catalog/dto/BusinessMetadataResponseDTO.java +302 -0
  22. src/main/java/com/dalab/catalog/dto/CatalogFiltersDTO.java +203 -0
  23. src/main/java/com/dalab/catalog/dto/EnhancedLineageDTO.java +462 -0
  24. src/main/java/com/dalab/catalog/dto/LabelAssignmentInputDTO.java +46 -0
  25. src/main/java/com/dalab/catalog/dto/LabelOutputDTO.java +91 -0
  26. src/main/java/com/dalab/catalog/dto/LineageAssetDTO.java +54 -0
  27. src/main/java/com/dalab/catalog/dto/LineageResponseDTO.java +32 -0
  28. src/main/java/com/dalab/catalog/dto/TaxonomyLabelDTO.java +76 -0
  29. src/main/java/com/dalab/catalog/dto/TaxonomyLabelsResponseDTO.java +21 -0
  30. src/main/java/com/dalab/catalog/mapper/AssetMapper.java +60 -0
  31. src/main/java/com/dalab/catalog/mapper/LabelTaxonomyMapper.java +54 -0
  32. src/main/java/com/dalab/catalog/model/Asset.java +191 -0
  33. src/main/java/com/dalab/catalog/model/AssetLineage.java +118 -0
  34. src/main/java/com/dalab/catalog/model/Label.java +110 -0
  35. src/main/java/com/dalab/catalog/repository/AssetLineageRepository.java +23 -0
  36. src/main/java/com/dalab/catalog/security/SecurityUtils.java +115 -0
  37. src/main/java/com/dalab/catalog/service/AssetService.java +1955 -0
  38. src/main/java/com/dalab/catalog/service/IAssetService.java +130 -0
  39. src/main/java/com/dalab/catalog/service/ILabelTaxonomyService.java +21 -0
  40. src/main/java/com/dalab/catalog/service/LabelTaxonomyService.java +146 -0
  41. src/main/java/com/dalab/catalog/web/rest/AssetController.java +375 -0
  42. src/main/java/com/dalab/catalog/web/rest/LabelTaxonomyController.java +85 -0
  43. src/main/resources/application.properties +91 -0
  44. src/test/java/com/dalab/catalog/controller/CatalogControllerTest.java +430 -0
  45. src/test/resources/application-test.yml +204 -0
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM openjdk:21-jdk-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install required packages
6
+ RUN apt-get update && apt-get install -y \
7
+ curl \
8
+ wget \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy application files
12
+ COPY . .
13
+
14
+ # Build application (if build.gradle.kts exists)
15
+ RUN if [ -f "build.gradle.kts" ]; then \
16
+ ./gradlew build -x test; \
17
+ fi
18
+
19
+ # Expose port
20
+ EXPOSE 8080
21
+
22
+ # Health check
23
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
24
+ CMD curl -f http://localhost:8080/actuator/health || exit 1
25
+
26
+ # Run application
27
+ CMD ["java", "-jar", "build/libs/da-catalog.jar"]
README.md CHANGED
@@ -1,10 +1,38 @@
1
  ---
2
- title: Da Catalog Dev
3
- emoji: 🏃
4
  colorFrom: blue
5
- colorTo: gray
6
  sdk: docker
7
- pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: da-catalog (dev)
3
+ emoji: 🔧
4
  colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
+ app_port: 8080
8
  ---
9
 
10
+ # da-catalog - dev Environment
11
+
12
+ This is the da-catalog microservice deployed in the dev environment.
13
+
14
+ ## Features
15
+
16
+ - RESTful API endpoints
17
+ - Health monitoring via Actuator
18
+ - JWT authentication integration
19
+ - PostgreSQL database connectivity
20
+
21
+ ## API Documentation
22
+
23
+ Once deployed, API documentation will be available at:
24
+ - Swagger UI: https://huggingface.co/spaces/dalabsai/da-catalog-dev/swagger-ui.html
25
+ - Health Check: https://huggingface.co/spaces/dalabsai/da-catalog-dev/actuator/health
26
+
27
+ ## Environment
28
+
29
+ - **Environment**: dev
30
+ - **Port**: 8080
31
+ - **Java Version**: 21
32
+ - **Framework**: Spring Boot
33
+
34
+ ## Deployment
35
+
36
+ This service is automatically deployed via the DALab CI/CD pipeline.
37
+
38
+ Last updated: 2025-06-16 23:39:42
build.gradle.kts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // da-catalog inherits common configuration from parent build.gradle.kts
2
+ // This build file adds catalog-specific dependencies
3
+
4
+ dependencies {
5
+ // Additional dependencies specific to da-catalog
6
+ implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.1.1")
7
+ }
8
+
9
+ // Configure main application class
10
+ configure<org.springframework.boot.gradle.dsl.SpringBootExtension> {
11
+ mainClass.set("com.dalab.catalog.DaCatalogApplication")
12
+ }
src/main/docker/Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-lean container using Google Distroless
2
+ # Expected final size: ~120-180MB (minimal base + JRE + JAR only)
3
+
4
+ FROM gcr.io/distroless/java21-debian12:nonroot
5
+
6
+ # Set working directory
7
+ WORKDIR /app
8
+
9
+ # Copy JAR file
10
+ COPY build/libs/da-catalog.jar app.jar
11
+
12
+ # Expose standard Spring Boot port
13
+ EXPOSE 8080
14
+
15
+ # Run application (distroless has no shell, so use exec form)
16
+ ENTRYPOINT ["java", \
17
+ "-XX:+UseContainerSupport", \
18
+ "-XX:MaxRAMPercentage=75.0", \
19
+ "-XX:+UseG1GC", \
20
+ "-XX:+UseStringDeduplication", \
21
+ "-Djava.security.egd=file:/dev/./urandom", \
22
+ "-Dspring.backgroundpreinitializer.ignore=true", \
23
+ "-jar", "app.jar"]
src/main/docker/Dockerfile.alpine-jlink ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-minimal Alpine + Custom JRE
2
+ # Expected size: ~120-160MB
3
+
4
+ # Stage 1: Create custom JRE with only needed modules
5
+ FROM eclipse-temurin:21-jdk-alpine as jre-builder
6
+ WORKDIR /app
7
+
8
+ # Analyze JAR to find required modules
9
+ COPY build/libs/*.jar app.jar
10
+ RUN jdeps --ignore-missing-deps --print-module-deps app.jar > modules.txt
11
+
12
+ # Create minimal JRE with only required modules
13
+ RUN jlink \
14
+ --add-modules $(cat modules.txt),java.logging,java.xml,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
15
+ --strip-debug \
16
+ --no-man-pages \
17
+ --no-header-files \
18
+ --compress=2 \
19
+ --output /custom-jre
20
+
21
+ # Stage 2: Production image
22
+ FROM alpine:3.19
23
+ RUN apk add --no-cache tzdata && \
24
+ addgroup -g 1001 -S appgroup && \
25
+ adduser -u 1001 -S appuser -G appgroup
26
+
27
+ # Copy custom JRE
28
+ COPY --from=jre-builder /custom-jre /opt/java
29
+ ENV JAVA_HOME=/opt/java
30
+ ENV PATH="$JAVA_HOME/bin:$PATH"
31
+
32
+ WORKDIR /app
33
+ COPY build/libs/*.jar app.jar
34
+ RUN chown appuser:appgroup app.jar
35
+
36
+ USER appuser
37
+ EXPOSE 8080
38
+
39
+ ENTRYPOINT ["java", \
40
+ "-XX:+UseContainerSupport", \
41
+ "-XX:MaxRAMPercentage=70.0", \
42
+ "-XX:+UseG1GC", \
43
+ "-jar", "app.jar"]
src/main/docker/Dockerfile.distroless ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-minimal container using Google Distroless
2
+ # Final image size should be ~120-150MB
3
+
4
+ FROM gcr.io/distroless/java21-debian12:nonroot
5
+
6
+ # Set working directory
7
+ WORKDIR /app
8
+
9
+ # Copy JAR file
10
+ COPY build/libs/da-catalog.jar app.jar
11
+
12
+ # Expose port
13
+ EXPOSE 8080
14
+
15
+ # Set optimized JVM options
16
+ ENV JAVA_OPTS="-XX:+UseContainerSupport \
17
+ -XX:MaxRAMPercentage=75.0 \
18
+ -XX:+UseG1GC \
19
+ -XX:+UseStringDeduplication \
20
+ -Djava.security.egd=file:/dev/./urandom"
21
+
22
+ # Run application (distroless images don't have shell)
23
+ ENTRYPOINT ["java", \
24
+ "-XX:+UseContainerSupport", \
25
+ "-XX:MaxRAMPercentage=75.0", \
26
+ "-XX:+UseG1GC", \
27
+ "-XX:+UseStringDeduplication", \
28
+ "-Djava.security.egd=file:/dev/./urandom", \
29
+ "-jar", "app.jar"]
src/main/docker/Dockerfile.layered ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-optimized layered build using Distroless
2
+ # Expected size: ~180-220MB with better caching
3
+
4
+ FROM gcr.io/distroless/java21-debian12:nonroot as base
5
+
6
+ # Stage 1: Extract JAR layers for optimal caching
7
+ FROM eclipse-temurin:21-jdk-alpine as extractor
8
+ WORKDIR /app
9
+ COPY build/libs/*.jar app.jar
10
+ RUN java -Djarmode=layertools -jar app.jar extract
11
+
12
+ # Stage 2: Production image with extracted layers
13
+ FROM base
14
+ WORKDIR /app
15
+
16
+ # Copy layers in dependency order (best caching)
17
+ COPY --from=extractor /app/dependencies/ ./
18
+ COPY --from=extractor /app/spring-boot-loader/ ./
19
+ COPY --from=extractor /app/snapshot-dependencies/ ./
20
+ COPY --from=extractor /app/application/ ./
21
+
22
+ EXPOSE 8080
23
+
24
+ # Optimized JVM settings for micro-containers
25
+ ENTRYPOINT ["java", \
26
+ "-XX:+UseContainerSupport", \
27
+ "-XX:MaxRAMPercentage=70.0", \
28
+ "-XX:+UseG1GC", \
29
+ "-XX:+UseStringDeduplication", \
30
+ "-XX:+CompactStrings", \
31
+ "-Xshare:on", \
32
+ "-Djava.security.egd=file:/dev/./urandom", \
33
+ "-Dspring.backgroundpreinitializer.ignore=true", \
34
+ "org.springframework.boot.loader.JarLauncher"]
src/main/docker/Dockerfile.native ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GraalVM Native Image - Ultra-fast startup, tiny size
2
+ # Expected size: ~50-80MB, startup <100ms
3
+ # Note: Requires native compilation support in Spring Boot
4
+
5
+ # Stage 1: Native compilation
6
+ FROM ghcr.io/graalvm/graalvm-ce:ol9-java21 as native-builder
7
+ WORKDIR /app
8
+
9
+ # Install native-image
10
+ RUN gu install native-image
11
+
12
+ # Copy source and build native executable
13
+ COPY . .
14
+ RUN ./gradlew nativeCompile
15
+
16
+ # Stage 2: Minimal runtime
17
+ FROM scratch
18
+ COPY --from=native-builder /app/build/native/nativeCompile/app /app
19
+ EXPOSE 8080
20
+ ENTRYPOINT ["/app"]
src/main/java/com/dalab/catalog/DaCatalogApplication.java ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+ import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
6
+ // import org.springframework.cloud.client.discovery.EnableDiscoveryClient; // If using Spring Cloud Discovery
7
+ // import org.springframework.cloud.openfeign.EnableFeignClients; // If this service will call others via Feign
8
+
9
+ @SpringBootApplication
10
+ @ConfigurationPropertiesScan // Scans for @ConfigurationProperties beans
11
+ // @EnableDiscoveryClient // Uncomment if using Eureka, Consul, etc.
12
+ // @EnableFeignClients // Uncomment if this service needs to call other services
13
+ // @EntityScan(basePackages = {"com.dalab.catalog.model", "com.dalab.common.model"}) // Add common model packages if needed
14
+ public class DaCatalogApplication {
15
+
16
+ public static void main(String[] args) {
17
+ SpringApplication.run(DaCatalogApplication.class, args);
18
+ }
19
+
20
+ }
src/main/java/com/dalab/catalog/common/ConflictException.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.common;
2
+
3
+ /**
4
+ * Exception thrown when a resource conflict occurs
5
+ */
6
+ public class ConflictException extends RuntimeException {
7
+
8
+ public ConflictException(String message) {
9
+ super(message);
10
+ }
11
+
12
+ public ConflictException(String message, Throwable cause) {
13
+ super(message, cause);
14
+ }
15
+ }
src/main/java/com/dalab/catalog/common/ResourceNotFoundException.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.common;
2
+
3
+ /**
4
+ * Exception thrown when a requested resource is not found
5
+ */
6
+ public class ResourceNotFoundException extends RuntimeException {
7
+
8
+ public ResourceNotFoundException(String message) {
9
+ super(message);
10
+ }
11
+
12
+ public ResourceNotFoundException(String resourceType, String field, String value) {
13
+ super(String.format("%s not found with %s: %s", resourceType, field, value));
14
+ }
15
+
16
+ public ResourceNotFoundException(String message, Throwable cause) {
17
+ super(message, cause);
18
+ }
19
+ }
src/main/java/com/dalab/catalog/dto/AssetComplianceStatusDTO.java ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+ import java.util.UUID;
7
+
8
+ import com.fasterxml.jackson.annotation.JsonInclude;
9
+
10
+ /**
11
+ * DTO for Asset Compliance Status information.
12
+ * Priority 2 endpoint: Provides detailed compliance status and monitoring for assets.
13
+ */
14
+ @JsonInclude(JsonInclude.Include.NON_NULL)
15
+ public class AssetComplianceStatusDTO {
16
+
17
+ private UUID assetId;
18
+ private String assetName;
19
+ private String overallComplianceStatus; // COMPLIANT, NON_COMPLIANT, PARTIALLY_COMPLIANT, UNKNOWN, MONITORING
20
+ private Double overallComplianceScore; // 0.0 to 100.0
21
+ private List<ComplianceFrameworkStatusDTO> frameworkCompliance;
22
+ private List<ComplianceViolationDTO> violations;
23
+ private List<ComplianceControlDTO> controls;
24
+ private ComplianceRiskAssessmentDTO riskAssessment;
25
+ private List<ComplianceRecommendationDTO> recommendations;
26
+ private ComplianceHistoryDTO complianceHistory;
27
+ private Instant lastEvaluated;
28
+ private String evaluatedBy;
29
+ private Instant nextEvaluation;
30
+
31
+ /**
32
+ * Default constructor.
33
+ */
34
+ public AssetComplianceStatusDTO() {}
35
+
36
+ /**
37
+ * Constructor with basic information.
38
+ */
39
+ public AssetComplianceStatusDTO(UUID assetId, String assetName) {
40
+ this.assetId = assetId;
41
+ this.assetName = assetName;
42
+ }
43
+
44
+ /**
45
+ * DTO for compliance framework status.
46
+ */
47
+ @JsonInclude(JsonInclude.Include.NON_NULL)
48
+ public static class ComplianceFrameworkStatusDTO {
49
+
50
+ private String frameworkName; // GDPR, SOX, HIPAA, PCI_DSS, ISO_27001, etc.
51
+ private String frameworkVersion;
52
+ private String complianceStatus;
53
+ private Double complianceScore;
54
+ private List<String> applicableControls;
55
+ private List<String> passedControls;
56
+ private List<String> failedControls;
57
+ private Integer totalRequirements;
58
+ private Integer metRequirements;
59
+ private Instant lastAssessment;
60
+ private String certificationStatus;
61
+ private Instant certificationExpiry;
62
+
63
+ public ComplianceFrameworkStatusDTO() {}
64
+
65
+ public ComplianceFrameworkStatusDTO(String frameworkName, String complianceStatus, Double complianceScore) {
66
+ this.frameworkName = frameworkName;
67
+ this.complianceStatus = complianceStatus;
68
+ this.complianceScore = complianceScore;
69
+ }
70
+
71
+ // Getters and setters
72
+ public String getFrameworkName() { return frameworkName; }
73
+ public void setFrameworkName(String frameworkName) { this.frameworkName = frameworkName; }
74
+ public String getFrameworkVersion() { return frameworkVersion; }
75
+ public void setFrameworkVersion(String frameworkVersion) { this.frameworkVersion = frameworkVersion; }
76
+ public String getComplianceStatus() { return complianceStatus; }
77
+ public void setComplianceStatus(String complianceStatus) { this.complianceStatus = complianceStatus; }
78
+ public Double getComplianceScore() { return complianceScore; }
79
+ public void setComplianceScore(Double complianceScore) { this.complianceScore = complianceScore; }
80
+ public List<String> getApplicableControls() { return applicableControls; }
81
+ public void setApplicableControls(List<String> applicableControls) { this.applicableControls = applicableControls; }
82
+ public List<String> getPassedControls() { return passedControls; }
83
+ public void setPassedControls(List<String> passedControls) { this.passedControls = passedControls; }
84
+ public List<String> getFailedControls() { return failedControls; }
85
+ public void setFailedControls(List<String> failedControls) { this.failedControls = failedControls; }
86
+ public Integer getTotalRequirements() { return totalRequirements; }
87
+ public void setTotalRequirements(Integer totalRequirements) { this.totalRequirements = totalRequirements; }
88
+ public Integer getMetRequirements() { return metRequirements; }
89
+ public void setMetRequirements(Integer metRequirements) { this.metRequirements = metRequirements; }
90
+ public Instant getLastAssessment() { return lastAssessment; }
91
+ public void setLastAssessment(Instant lastAssessment) { this.lastAssessment = lastAssessment; }
92
+ public String getCertificationStatus() { return certificationStatus; }
93
+ public void setCertificationStatus(String certificationStatus) { this.certificationStatus = certificationStatus; }
94
+ public Instant getCertificationExpiry() { return certificationExpiry; }
95
+ public void setCertificationExpiry(Instant certificationExpiry) { this.certificationExpiry = certificationExpiry; }
96
+ }
97
+
98
+ /**
99
+ * DTO for compliance violations.
100
+ */
101
+ @JsonInclude(JsonInclude.Include.NON_NULL)
102
+ public static class ComplianceViolationDTO {
103
+
104
+ private String violationId;
105
+ private String violationType; // DATA_RETENTION, ACCESS_CONTROL, ENCRYPTION, AUDIT_TRAIL, etc.
106
+ private String severity; // CRITICAL, HIGH, MEDIUM, LOW
107
+ private String description;
108
+ private String framework;
109
+ private String controlId;
110
+ private String currentValue;
111
+ private String expectedValue;
112
+ private Instant detectedAt;
113
+ private String detectedBy;
114
+ private String status; // OPEN, IN_PROGRESS, RESOLVED, ACCEPTED_RISK
115
+ private String assignedTo;
116
+ private Instant dueDate;
117
+ private List<String> remediationActions;
118
+ private Map<String, Object> evidence;
119
+
120
+ public ComplianceViolationDTO() {}
121
+
122
+ public ComplianceViolationDTO(String violationId, String violationType, String severity) {
123
+ this.violationId = violationId;
124
+ this.violationType = violationType;
125
+ this.severity = severity;
126
+ }
127
+
128
+ // Getters and setters
129
+ public String getViolationId() { return violationId; }
130
+ public void setViolationId(String violationId) { this.violationId = violationId; }
131
+ public String getViolationType() { return violationType; }
132
+ public void setViolationType(String violationType) { this.violationType = violationType; }
133
+ public String getSeverity() { return severity; }
134
+ public void setSeverity(String severity) { this.severity = severity; }
135
+ public String getDescription() { return description; }
136
+ public void setDescription(String description) { this.description = description; }
137
+ public String getFramework() { return framework; }
138
+ public void setFramework(String framework) { this.framework = framework; }
139
+ public String getControlId() { return controlId; }
140
+ public void setControlId(String controlId) { this.controlId = controlId; }
141
+ public String getCurrentValue() { return currentValue; }
142
+ public void setCurrentValue(String currentValue) { this.currentValue = currentValue; }
143
+ public String getExpectedValue() { return expectedValue; }
144
+ public void setExpectedValue(String expectedValue) { this.expectedValue = expectedValue; }
145
+ public Instant getDetectedAt() { return detectedAt; }
146
+ public void setDetectedAt(Instant detectedAt) { this.detectedAt = detectedAt; }
147
+ public String getDetectedBy() { return detectedBy; }
148
+ public void setDetectedBy(String detectedBy) { this.detectedBy = detectedBy; }
149
+ public String getStatus() { return status; }
150
+ public void setStatus(String status) { this.status = status; }
151
+ public String getAssignedTo() { return assignedTo; }
152
+ public void setAssignedTo(String assignedTo) { this.assignedTo = assignedTo; }
153
+ public Instant getDueDate() { return dueDate; }
154
+ public void setDueDate(Instant dueDate) { this.dueDate = dueDate; }
155
+ public List<String> getRemediationActions() { return remediationActions; }
156
+ public void setRemediationActions(List<String> remediationActions) { this.remediationActions = remediationActions; }
157
+ public Map<String, Object> getEvidence() { return evidence; }
158
+ public void setEvidence(Map<String, Object> evidence) { this.evidence = evidence; }
159
+ }
160
+
161
+ /**
162
+ * DTO for compliance controls.
163
+ */
164
+ @JsonInclude(JsonInclude.Include.NON_NULL)
165
+ public static class ComplianceControlDTO {
166
+
167
+ private String controlId;
168
+ private String controlName;
169
+ private String controlType; // PREVENTIVE, DETECTIVE, CORRECTIVE
170
+ private String framework;
171
+ private String description;
172
+ private String status; // PASSED, FAILED, NOT_APPLICABLE, MANUAL_REVIEW
173
+ private String implementationStatus; // IMPLEMENTED, PARTIALLY_IMPLEMENTED, NOT_IMPLEMENTED
174
+ private Double effectivenessScore;
175
+ private Instant lastTested;
176
+ private String testedBy;
177
+ private Instant nextTest;
178
+ private String testFrequency; // DAILY, WEEKLY, MONTHLY, QUARTERLY, ANNUALLY
179
+ private List<String> evidenceRequired;
180
+ private Map<String, Object> testResults;
181
+
182
+ public ComplianceControlDTO() {}
183
+
184
+ public ComplianceControlDTO(String controlId, String controlName, String status) {
185
+ this.controlId = controlId;
186
+ this.controlName = controlName;
187
+ this.status = status;
188
+ }
189
+
190
+ // Getters and setters
191
+ public String getControlId() { return controlId; }
192
+ public void setControlId(String controlId) { this.controlId = controlId; }
193
+ public String getControlName() { return controlName; }
194
+ public void setControlName(String controlName) { this.controlName = controlName; }
195
+ public String getControlType() { return controlType; }
196
+ public void setControlType(String controlType) { this.controlType = controlType; }
197
+ public String getFramework() { return framework; }
198
+ public void setFramework(String framework) { this.framework = framework; }
199
+ public String getDescription() { return description; }
200
+ public void setDescription(String description) { this.description = description; }
201
+ public String getStatus() { return status; }
202
+ public void setStatus(String status) { this.status = status; }
203
+ public String getImplementationStatus() { return implementationStatus; }
204
+ public void setImplementationStatus(String implementationStatus) { this.implementationStatus = implementationStatus; }
205
+ public Double getEffectivenessScore() { return effectivenessScore; }
206
+ public void setEffectivenessScore(Double effectivenessScore) { this.effectivenessScore = effectivenessScore; }
207
+ public Instant getLastTested() { return lastTested; }
208
+ public void setLastTested(Instant lastTested) { this.lastTested = lastTested; }
209
+ public String getTestedBy() { return testedBy; }
210
+ public void setTestedBy(String testedBy) { this.testedBy = testedBy; }
211
+ public Instant getNextTest() { return nextTest; }
212
+ public void setNextTest(Instant nextTest) { this.nextTest = nextTest; }
213
+ public String getTestFrequency() { return testFrequency; }
214
+ public void setTestFrequency(String testFrequency) { this.testFrequency = testFrequency; }
215
+ public List<String> getEvidenceRequired() { return evidenceRequired; }
216
+ public void setEvidenceRequired(List<String> evidenceRequired) { this.evidenceRequired = evidenceRequired; }
217
+ public Map<String, Object> getTestResults() { return testResults; }
218
+ public void setTestResults(Map<String, Object> testResults) { this.testResults = testResults; }
219
+ }
220
+
221
+ /**
222
+ * DTO for compliance risk assessment.
223
+ */
224
+ @JsonInclude(JsonInclude.Include.NON_NULL)
225
+ public static class ComplianceRiskAssessmentDTO {
226
+
227
+ private String overallRiskLevel; // CRITICAL, HIGH, MEDIUM, LOW
228
+ private Double riskScore; // 0.0 to 100.0
229
+ private List<String> riskFactors;
230
+ private Map<String, String> riskCategories; // category -> risk level
231
+ private String residualRisk;
232
+ private List<String> mitigationMeasures;
233
+ private Instant lastAssessed;
234
+ private String assessedBy;
235
+ private Instant nextAssessment;
236
+
237
+ public ComplianceRiskAssessmentDTO() {}
238
+
239
+ // Getters and setters
240
+ public String getOverallRiskLevel() { return overallRiskLevel; }
241
+ public void setOverallRiskLevel(String overallRiskLevel) { this.overallRiskLevel = overallRiskLevel; }
242
+ public Double getRiskScore() { return riskScore; }
243
+ public void setRiskScore(Double riskScore) { this.riskScore = riskScore; }
244
+ public List<String> getRiskFactors() { return riskFactors; }
245
+ public void setRiskFactors(List<String> riskFactors) { this.riskFactors = riskFactors; }
246
+ public Map<String, String> getRiskCategories() { return riskCategories; }
247
+ public void setRiskCategories(Map<String, String> riskCategories) { this.riskCategories = riskCategories; }
248
+ public String getResidualRisk() { return residualRisk; }
249
+ public void setResidualRisk(String residualRisk) { this.residualRisk = residualRisk; }
250
+ public List<String> getMitigationMeasures() { return mitigationMeasures; }
251
+ public void setMitigationMeasures(List<String> mitigationMeasures) { this.mitigationMeasures = mitigationMeasures; }
252
+ public Instant getLastAssessed() { return lastAssessed; }
253
+ public void setLastAssessed(Instant lastAssessed) { this.lastAssessed = lastAssessed; }
254
+ public String getAssessedBy() { return assessedBy; }
255
+ public void setAssessedBy(String assessedBy) { this.assessedBy = assessedBy; }
256
+ public Instant getNextAssessment() { return nextAssessment; }
257
+ public void setNextAssessment(Instant nextAssessment) { this.nextAssessment = nextAssessment; }
258
+ }
259
+
260
+ /**
261
+ * DTO for compliance recommendations.
262
+ */
263
+ @JsonInclude(JsonInclude.Include.NON_NULL)
264
+ public static class ComplianceRecommendationDTO {
265
+
266
+ private String recommendationId;
267
+ private String type; // REMEDIATION, ENHANCEMENT, PREVENTIVE
268
+ private String priority; // CRITICAL, HIGH, MEDIUM, LOW
269
+ private String title;
270
+ private String description;
271
+ private String framework;
272
+ private List<String> affectedControls;
273
+ private String actionRequired;
274
+ private Integer estimatedEffort; // in hours
275
+ private String costEstimate;
276
+ private String expectedOutcome;
277
+ private String assignedTo;
278
+ private Instant createdAt;
279
+ private Instant targetDate;
280
+
281
+ public ComplianceRecommendationDTO() {}
282
+
283
+ public ComplianceRecommendationDTO(String recommendationId, String type, String priority) {
284
+ this.recommendationId = recommendationId;
285
+ this.type = type;
286
+ this.priority = priority;
287
+ }
288
+
289
+ // Getters and setters
290
+ public String getRecommendationId() { return recommendationId; }
291
+ public void setRecommendationId(String recommendationId) { this.recommendationId = recommendationId; }
292
+ public String getType() { return type; }
293
+ public void setType(String type) { this.type = type; }
294
+ public String getPriority() { return priority; }
295
+ public void setPriority(String priority) { this.priority = priority; }
296
+ public String getTitle() { return title; }
297
+ public void setTitle(String title) { this.title = title; }
298
+ public String getDescription() { return description; }
299
+ public void setDescription(String description) { this.description = description; }
300
+ public String getFramework() { return framework; }
301
+ public void setFramework(String framework) { this.framework = framework; }
302
+ public List<String> getAffectedControls() { return affectedControls; }
303
+ public void setAffectedControls(List<String> affectedControls) { this.affectedControls = affectedControls; }
304
+ public String getActionRequired() { return actionRequired; }
305
+ public void setActionRequired(String actionRequired) { this.actionRequired = actionRequired; }
306
+ public Integer getEstimatedEffort() { return estimatedEffort; }
307
+ public void setEstimatedEffort(Integer estimatedEffort) { this.estimatedEffort = estimatedEffort; }
308
+ public String getCostEstimate() { return costEstimate; }
309
+ public void setCostEstimate(String costEstimate) { this.costEstimate = costEstimate; }
310
+ public String getExpectedOutcome() { return expectedOutcome; }
311
+ public void setExpectedOutcome(String expectedOutcome) { this.expectedOutcome = expectedOutcome; }
312
+ public String getAssignedTo() { return assignedTo; }
313
+ public void setAssignedTo(String assignedTo) { this.assignedTo = assignedTo; }
314
+ public Instant getCreatedAt() { return createdAt; }
315
+ public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
316
+ public Instant getTargetDate() { return targetDate; }
317
+ public void setTargetDate(Instant targetDate) { this.targetDate = targetDate; }
318
+ }
319
+
320
+ /**
321
+ * DTO for compliance history tracking.
322
+ */
323
+ @JsonInclude(JsonInclude.Include.NON_NULL)
324
+ public static class ComplianceHistoryDTO {
325
+
326
+ private List<ComplianceEventDTO> recentEvents;
327
+ private Map<String, Double> scoreHistory; // timestamp -> score
328
+ private List<String> trendAnalysis;
329
+ private Integer totalEvaluations;
330
+ private Instant firstEvaluation;
331
+ private Double averageScore;
332
+ private String trend; // IMPROVING, DECLINING, STABLE
333
+
334
+ public ComplianceHistoryDTO() {}
335
+
336
+ // Getters and setters
337
+ public List<ComplianceEventDTO> getRecentEvents() { return recentEvents; }
338
+ public void setRecentEvents(List<ComplianceEventDTO> recentEvents) { this.recentEvents = recentEvents; }
339
+ public Map<String, Double> getScoreHistory() { return scoreHistory; }
340
+ public void setScoreHistory(Map<String, Double> scoreHistory) { this.scoreHistory = scoreHistory; }
341
+ public List<String> getTrendAnalysis() { return trendAnalysis; }
342
+ public void setTrendAnalysis(List<String> trendAnalysis) { this.trendAnalysis = trendAnalysis; }
343
+ public Integer getTotalEvaluations() { return totalEvaluations; }
344
+ public void setTotalEvaluations(Integer totalEvaluations) { this.totalEvaluations = totalEvaluations; }
345
+ public Instant getFirstEvaluation() { return firstEvaluation; }
346
+ public void setFirstEvaluation(Instant firstEvaluation) { this.firstEvaluation = firstEvaluation; }
347
+ public Double getAverageScore() { return averageScore; }
348
+ public void setAverageScore(Double averageScore) { this.averageScore = averageScore; }
349
+ public String getTrend() { return trend; }
350
+ public void setTrend(String trend) { this.trend = trend; }
351
+ }
352
+
353
+ /**
354
+ * DTO for compliance events.
355
+ */
356
+ @JsonInclude(JsonInclude.Include.NON_NULL)
357
+ public static class ComplianceEventDTO {
358
+
359
+ private String eventType; // EVALUATION, VIOLATION_DETECTED, VIOLATION_RESOLVED, CONTROL_UPDATED
360
+ private String description;
361
+ private Instant timestamp;
362
+ private String triggeredBy;
363
+ private Map<String, Object> details;
364
+
365
+ public ComplianceEventDTO() {}
366
+
367
+ public ComplianceEventDTO(String eventType, String description, Instant timestamp) {
368
+ this.eventType = eventType;
369
+ this.description = description;
370
+ this.timestamp = timestamp;
371
+ }
372
+
373
+ // Getters and setters
374
+ public String getEventType() { return eventType; }
375
+ public void setEventType(String eventType) { this.eventType = eventType; }
376
+ public String getDescription() { return description; }
377
+ public void setDescription(String description) { this.description = description; }
378
+ public Instant getTimestamp() { return timestamp; }
379
+ public void setTimestamp(Instant timestamp) { this.timestamp = timestamp; }
380
+ public String getTriggeredBy() { return triggeredBy; }
381
+ public void setTriggeredBy(String triggeredBy) { this.triggeredBy = triggeredBy; }
382
+ public Map<String, Object> getDetails() { return details; }
383
+ public void setDetails(Map<String, Object> details) { this.details = details; }
384
+ }
385
+
386
+ // Main class getters and setters
387
+ public UUID getAssetId() { return assetId; }
388
+ public void setAssetId(UUID assetId) { this.assetId = assetId; }
389
+ public String getAssetName() { return assetName; }
390
+ public void setAssetName(String assetName) { this.assetName = assetName; }
391
+ public String getOverallComplianceStatus() { return overallComplianceStatus; }
392
+ public void setOverallComplianceStatus(String overallComplianceStatus) { this.overallComplianceStatus = overallComplianceStatus; }
393
+ public Double getOverallComplianceScore() { return overallComplianceScore; }
394
+ public void setOverallComplianceScore(Double overallComplianceScore) { this.overallComplianceScore = overallComplianceScore; }
395
+ public List<ComplianceFrameworkStatusDTO> getFrameworkCompliance() { return frameworkCompliance; }
396
+ public void setFrameworkCompliance(List<ComplianceFrameworkStatusDTO> frameworkCompliance) { this.frameworkCompliance = frameworkCompliance; }
397
+ public List<ComplianceViolationDTO> getViolations() { return violations; }
398
+ public void setViolations(List<ComplianceViolationDTO> violations) { this.violations = violations; }
399
+ public List<ComplianceControlDTO> getControls() { return controls; }
400
+ public void setControls(List<ComplianceControlDTO> controls) { this.controls = controls; }
401
+ public ComplianceRiskAssessmentDTO getRiskAssessment() { return riskAssessment; }
402
+ public void setRiskAssessment(ComplianceRiskAssessmentDTO riskAssessment) { this.riskAssessment = riskAssessment; }
403
+ public List<ComplianceRecommendationDTO> getRecommendations() { return recommendations; }
404
+ public void setRecommendations(List<ComplianceRecommendationDTO> recommendations) { this.recommendations = recommendations; }
405
+ public ComplianceHistoryDTO getComplianceHistory() { return complianceHistory; }
406
+ public void setComplianceHistory(ComplianceHistoryDTO complianceHistory) { this.complianceHistory = complianceHistory; }
407
+ public Instant getLastEvaluated() { return lastEvaluated; }
408
+ public void setLastEvaluated(Instant lastEvaluated) { this.lastEvaluated = lastEvaluated; }
409
+ public String getEvaluatedBy() { return evaluatedBy; }
410
+ public void setEvaluatedBy(String evaluatedBy) { this.evaluatedBy = evaluatedBy; }
411
+ public Instant getNextEvaluation() { return nextEvaluation; }
412
+ public void setNextEvaluation(Instant nextEvaluation) { this.nextEvaluation = nextEvaluation; }
413
+ }
src/main/java/com/dalab/catalog/dto/AssetInputDTO.java ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.List;
4
+ import java.util.Map;
5
+ import java.util.UUID;
6
+
7
+ import jakarta.validation.constraints.NotBlank;
8
+ import jakarta.validation.constraints.NotNull;
9
+ import jakarta.validation.constraints.Size;
10
+
11
+ public class AssetInputDTO {
12
+
13
+ @NotBlank(message = "Asset name is required")
14
+ @Size(max = 255)
15
+ private String assetName;
16
+
17
+ @NotNull(message = "Cloud Connection ID is required")
18
+ private UUID cloudConnectionId;
19
+
20
+ @NotBlank(message = "Cloud provider is required")
21
+ private String cloudProvider; // e.g., GCP, AWS, AZURE, OCI
22
+
23
+ @NotBlank(message = "Asset type is required")
24
+ private String assetType; // e.g., "BigQuery Dataset", "S3 Bucket"
25
+
26
+ @NotBlank(message = "Native asset ID is required")
27
+ @Size(max = 1024)
28
+ private String nativeAssetId;
29
+
30
+ @Size(max = 255)
31
+ private String region;
32
+
33
+ private Map<String, Object> detailsJson; // Provider-specific metadata
34
+
35
+ // Optional: initial set of labels to apply
36
+ private List<String> labels;
37
+
38
+ // createdByUserId will be set by the system/admin context, not directly from DTO
39
+ // discoveredAt, lastScannedAt will be set by the system
40
+
41
+ // Getters and Setters
42
+ public String getAssetName() {
43
+ return assetName;
44
+ }
45
+
46
+ public void setAssetName(String assetName) {
47
+ this.assetName = assetName;
48
+ }
49
+
50
+ public UUID getCloudConnectionId() {
51
+ return cloudConnectionId;
52
+ }
53
+
54
+ public void setCloudConnectionId(UUID cloudConnectionId) {
55
+ this.cloudConnectionId = cloudConnectionId;
56
+ }
57
+
58
+ public String getCloudProvider() {
59
+ return cloudProvider;
60
+ }
61
+
62
+ public void setCloudProvider(String cloudProvider) {
63
+ this.cloudProvider = cloudProvider;
64
+ }
65
+
66
+ public String getAssetType() {
67
+ return assetType;
68
+ }
69
+
70
+ public void setAssetType(String assetType) {
71
+ this.assetType = assetType;
72
+ }
73
+
74
+ public String getNativeAssetId() {
75
+ return nativeAssetId;
76
+ }
77
+
78
+ public void setNativeAssetId(String nativeAssetId) {
79
+ this.nativeAssetId = nativeAssetId;
80
+ }
81
+
82
+ public String getRegion() {
83
+ return region;
84
+ }
85
+
86
+ public void setRegion(String region) {
87
+ this.region = region;
88
+ }
89
+
90
+ public Map<String, Object> getDetailsJson() {
91
+ return detailsJson;
92
+ }
93
+
94
+ public void setDetailsJson(Map<String, Object> detailsJson) {
95
+ this.detailsJson = detailsJson;
96
+ }
97
+
98
+ public List<String> getLabels() {
99
+ return labels;
100
+ }
101
+
102
+ public void setLabels(List<String> labels) {
103
+ this.labels = labels;
104
+ }
105
+ }
src/main/java/com/dalab/catalog/dto/AssetLabelsPostRequestDTO.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.List;
4
+
5
+ import jakarta.validation.Valid;
6
+ import jakarta.validation.constraints.NotEmpty;
7
+
8
+ public class AssetLabelsPostRequestDTO {
9
+
10
+ @NotEmpty
11
+ @Valid // To enable validation on elements of the list
12
+ private List<LabelAssignmentInputDTO> labels;
13
+
14
+ // Getters and Setters
15
+ public List<LabelAssignmentInputDTO> getLabels() {
16
+ return labels;
17
+ }
18
+
19
+ public void setLabels(List<LabelAssignmentInputDTO> labels) {
20
+ this.labels = labels;
21
+ }
22
+ }
src/main/java/com/dalab/catalog/dto/AssetLabelsResponseDTO.java ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.List;
4
+
5
+ public class AssetLabelsResponseDTO {
6
+ private List<LabelOutputDTO> labels;
7
+
8
+ public AssetLabelsResponseDTO() {}
9
+
10
+ public AssetLabelsResponseDTO(List<LabelOutputDTO> labels) {
11
+ this.labels = labels;
12
+ }
13
+
14
+ public List<LabelOutputDTO> getLabels() {
15
+ return labels;
16
+ }
17
+
18
+ public void setLabels(List<LabelOutputDTO> labels) {
19
+ this.labels = labels;
20
+ }
21
+ }
src/main/java/com/dalab/catalog/dto/AssetOutputDTO.java ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+ import java.util.UUID;
7
+
8
+ import com.fasterxml.jackson.annotation.JsonInclude;
9
+
10
+ @JsonInclude(JsonInclude.Include.NON_NULL)
11
+ public class AssetOutputDTO {
12
+
13
+ private UUID assetId;
14
+ private String assetName;
15
+ private UUID cloudConnectionId;
16
+ private String cloudProvider;
17
+ private String assetType;
18
+ private String nativeAssetId;
19
+ private String region;
20
+ private Map<String, Object> detailsJson; // Or a more structured DTO if details are well-defined
21
+ private Instant discoveredAt;
22
+ private Instant lastScannedAt;
23
+ private UUID createdByUserId; // Or a UserSummaryDTO
24
+ private Instant createdAt;
25
+ private Instant updatedAt;
26
+ private List<String> labels; // List of applied label names
27
+ private Map<String, Object> businessMetadataJson;
28
+ // private List<LabelDTO> labels; // Or list of LabelDTOs for more details
29
+
30
+ // Getters and Setters
31
+ public UUID getAssetId() {
32
+ return assetId;
33
+ }
34
+
35
+ public void setAssetId(UUID assetId) {
36
+ this.assetId = assetId;
37
+ }
38
+
39
+ public String getAssetName() {
40
+ return assetName;
41
+ }
42
+
43
+ public void setAssetName(String assetName) {
44
+ this.assetName = assetName;
45
+ }
46
+
47
+ public UUID getCloudConnectionId() {
48
+ return cloudConnectionId;
49
+ }
50
+
51
+ public void setCloudConnectionId(UUID cloudConnectionId) {
52
+ this.cloudConnectionId = cloudConnectionId;
53
+ }
54
+
55
+ public String getCloudProvider() {
56
+ return cloudProvider;
57
+ }
58
+
59
+ public void setCloudProvider(String cloudProvider) {
60
+ this.cloudProvider = cloudProvider;
61
+ }
62
+
63
+ public String getAssetType() {
64
+ return assetType;
65
+ }
66
+
67
+ public void setAssetType(String assetType) {
68
+ this.assetType = assetType;
69
+ }
70
+
71
+ public String getNativeAssetId() {
72
+ return nativeAssetId;
73
+ }
74
+
75
+ public void setNativeAssetId(String nativeAssetId) {
76
+ this.nativeAssetId = nativeAssetId;
77
+ }
78
+
79
+ public String getRegion() {
80
+ return region;
81
+ }
82
+
83
+ public void setRegion(String region) {
84
+ this.region = region;
85
+ }
86
+
87
+ public Map<String, Object> getDetailsJson() {
88
+ return detailsJson;
89
+ }
90
+
91
+ public void setDetailsJson(Map<String, Object> detailsJson) {
92
+ this.detailsJson = detailsJson;
93
+ }
94
+
95
+ public Instant getDiscoveredAt() {
96
+ return discoveredAt;
97
+ }
98
+
99
+ public void setDiscoveredAt(Instant discoveredAt) {
100
+ this.discoveredAt = discoveredAt;
101
+ }
102
+
103
+ public Instant getLastScannedAt() {
104
+ return lastScannedAt;
105
+ }
106
+
107
+ public void setLastScannedAt(Instant lastScannedAt) {
108
+ this.lastScannedAt = lastScannedAt;
109
+ }
110
+
111
+ public UUID getCreatedByUserId() {
112
+ return createdByUserId;
113
+ }
114
+
115
+ public void setCreatedByUserId(UUID createdByUserId) {
116
+ this.createdByUserId = createdByUserId;
117
+ }
118
+
119
+ public Instant getCreatedAt() {
120
+ return createdAt;
121
+ }
122
+
123
+ public void setCreatedAt(Instant createdAt) {
124
+ this.createdAt = createdAt;
125
+ }
126
+
127
+ public Instant getUpdatedAt() {
128
+ return updatedAt;
129
+ }
130
+
131
+ public void setUpdatedAt(Instant updatedAt) {
132
+ this.updatedAt = updatedAt;
133
+ }
134
+
135
+ public List<String> getLabels() {
136
+ return labels;
137
+ }
138
+
139
+ public void setLabels(List<String> labels) {
140
+ this.labels = labels;
141
+ }
142
+
143
+ public Map<String, Object> getBusinessMetadataJson() {
144
+ return businessMetadataJson;
145
+ }
146
+
147
+ public void setBusinessMetadataJson(Map<String, Object> businessMetadataJson) {
148
+ this.businessMetadataJson = businessMetadataJson;
149
+ }
150
+ }
src/main/java/com/dalab/catalog/dto/AssetPolicyMappingDTO.java ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+ import java.util.UUID;
7
+
8
+ import com.fasterxml.jackson.annotation.JsonInclude;
9
+
10
+ /**
11
+ * DTO for Asset-Policy Mapping information.
12
+ * Priority 2 endpoint: Shows which policies apply to an asset and their compliance status.
13
+ */
14
+ @JsonInclude(JsonInclude.Include.NON_NULL)
15
+ public class AssetPolicyMappingDTO {
16
+
17
+ private UUID assetId;
18
+ private String assetName;
19
+ private List<PolicyMappingDTO> applicablePolicies;
20
+ private List<PolicyMappingDTO> inheritedPolicies;
21
+ private PolicyComplianceSummaryDTO complianceSummary;
22
+ private List<PolicyRiskDTO> riskAssessment;
23
+ private List<PolicyRecommendationDTO> recommendations;
24
+ private Instant lastEvaluated;
25
+ private String evaluationStatus;
26
+
27
+ /**
28
+ * Default constructor.
29
+ */
30
+ public AssetPolicyMappingDTO() {}
31
+
32
+ /**
33
+ * Constructor with basic information.
34
+ */
35
+ public AssetPolicyMappingDTO(UUID assetId, String assetName) {
36
+ this.assetId = assetId;
37
+ this.assetName = assetName;
38
+ }
39
+
40
+ /**
41
+ * DTO for individual policy mapping information.
42
+ */
43
+ @JsonInclude(JsonInclude.Include.NON_NULL)
44
+ public static class PolicyMappingDTO {
45
+
46
+ private UUID policyId;
47
+ private String policyName;
48
+ private String policyType; // DATA_GOVERNANCE, SECURITY, COMPLIANCE, RETENTION, etc.
49
+ private String policyVersion;
50
+ private String applicabilityReason; // DIRECT, INHERITED, LABEL_BASED, TYPE_BASED
51
+ private String complianceStatus; // COMPLIANT, NON_COMPLIANT, PARTIALLY_COMPLIANT, UNKNOWN
52
+ private Double complianceScore; // 0.0 to 100.0
53
+ private List<PolicyRuleComplianceDTO> ruleCompliance;
54
+ private Instant lastEvaluated;
55
+ private String evaluatedBy;
56
+ private List<String> exemptions;
57
+ private Map<String, Object> metadata;
58
+
59
+ public PolicyMappingDTO() {}
60
+
61
+ public PolicyMappingDTO(UUID policyId, String policyName, String policyType) {
62
+ this.policyId = policyId;
63
+ this.policyName = policyName;
64
+ this.policyType = policyType;
65
+ }
66
+
67
+ // Getters and setters
68
+ public UUID getPolicyId() { return policyId; }
69
+ public void setPolicyId(UUID policyId) { this.policyId = policyId; }
70
+ public String getPolicyName() { return policyName; }
71
+ public void setPolicyName(String policyName) { this.policyName = policyName; }
72
+ public String getPolicyType() { return policyType; }
73
+ public void setPolicyType(String policyType) { this.policyType = policyType; }
74
+ public String getPolicyVersion() { return policyVersion; }
75
+ public void setPolicyVersion(String policyVersion) { this.policyVersion = policyVersion; }
76
+ public String getApplicabilityReason() { return applicabilityReason; }
77
+ public void setApplicabilityReason(String applicabilityReason) { this.applicabilityReason = applicabilityReason; }
78
+ public String getComplianceStatus() { return complianceStatus; }
79
+ public void setComplianceStatus(String complianceStatus) { this.complianceStatus = complianceStatus; }
80
+ public Double getComplianceScore() { return complianceScore; }
81
+ public void setComplianceScore(Double complianceScore) { this.complianceScore = complianceScore; }
82
+ public List<PolicyRuleComplianceDTO> getRuleCompliance() { return ruleCompliance; }
83
+ public void setRuleCompliance(List<PolicyRuleComplianceDTO> ruleCompliance) { this.ruleCompliance = ruleCompliance; }
84
+ public Instant getLastEvaluated() { return lastEvaluated; }
85
+ public void setLastEvaluated(Instant lastEvaluated) { this.lastEvaluated = lastEvaluated; }
86
+ public String getEvaluatedBy() { return evaluatedBy; }
87
+ public void setEvaluatedBy(String evaluatedBy) { this.evaluatedBy = evaluatedBy; }
88
+ public List<String> getExemptions() { return exemptions; }
89
+ public void setExemptions(List<String> exemptions) { this.exemptions = exemptions; }
90
+ public Map<String, Object> getMetadata() { return metadata; }
91
+ public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
92
+ }
93
+
94
+ /**
95
+ * DTO for policy rule compliance details.
96
+ */
97
+ @JsonInclude(JsonInclude.Include.NON_NULL)
98
+ public static class PolicyRuleComplianceDTO {
99
+
100
+ private String ruleId;
101
+ private String ruleName;
102
+ private String ruleType;
103
+ private String complianceStatus;
104
+ private String actualValue;
105
+ private String expectedValue;
106
+ private String nonComplianceReason;
107
+ private String severity; // CRITICAL, HIGH, MEDIUM, LOW
108
+ private List<String> recommendedActions;
109
+
110
+ public PolicyRuleComplianceDTO() {}
111
+
112
+ public PolicyRuleComplianceDTO(String ruleId, String ruleName, String complianceStatus) {
113
+ this.ruleId = ruleId;
114
+ this.ruleName = ruleName;
115
+ this.complianceStatus = complianceStatus;
116
+ }
117
+
118
+ // Getters and setters
119
+ public String getRuleId() { return ruleId; }
120
+ public void setRuleId(String ruleId) { this.ruleId = ruleId; }
121
+ public String getRuleName() { return ruleName; }
122
+ public void setRuleName(String ruleName) { this.ruleName = ruleName; }
123
+ public String getRuleType() { return ruleType; }
124
+ public void setRuleType(String ruleType) { this.ruleType = ruleType; }
125
+ public String getComplianceStatus() { return complianceStatus; }
126
+ public void setComplianceStatus(String complianceStatus) { this.complianceStatus = complianceStatus; }
127
+ public String getActualValue() { return actualValue; }
128
+ public void setActualValue(String actualValue) { this.actualValue = actualValue; }
129
+ public String getExpectedValue() { return expectedValue; }
130
+ public void setExpectedValue(String expectedValue) { this.expectedValue = expectedValue; }
131
+ public String getNonComplianceReason() { return nonComplianceReason; }
132
+ public void setNonComplianceReason(String nonComplianceReason) { this.nonComplianceReason = nonComplianceReason; }
133
+ public String getSeverity() { return severity; }
134
+ public void setSeverity(String severity) { this.severity = severity; }
135
+ public List<String> getRecommendedActions() { return recommendedActions; }
136
+ public void setRecommendedActions(List<String> recommendedActions) { this.recommendedActions = recommendedActions; }
137
+ }
138
+
139
+ /**
140
+ * DTO for overall policy compliance summary.
141
+ */
142
+ @JsonInclude(JsonInclude.Include.NON_NULL)
143
+ public static class PolicyComplianceSummaryDTO {
144
+
145
+ private Integer totalPolicies;
146
+ private Integer compliantPolicies;
147
+ private Integer nonCompliantPolicies;
148
+ private Integer partiallyCompliantPolicies;
149
+ private Double overallComplianceScore;
150
+ private String overallStatus;
151
+ private Map<String, Integer> policyTypeBreakdown;
152
+ private Map<String, Integer> complianceStatusBreakdown;
153
+ private List<String> criticalIssues;
154
+ private Instant lastFullEvaluation;
155
+
156
+ public PolicyComplianceSummaryDTO() {}
157
+
158
+ // Getters and setters
159
+ public Integer getTotalPolicies() { return totalPolicies; }
160
+ public void setTotalPolicies(Integer totalPolicies) { this.totalPolicies = totalPolicies; }
161
+ public Integer getCompliantPolicies() { return compliantPolicies; }
162
+ public void setCompliantPolicies(Integer compliantPolicies) { this.compliantPolicies = compliantPolicies; }
163
+ public Integer getNonCompliantPolicies() { return nonCompliantPolicies; }
164
+ public void setNonCompliantPolicies(Integer nonCompliantPolicies) { this.nonCompliantPolicies = nonCompliantPolicies; }
165
+ public Integer getPartiallyCompliantPolicies() { return partiallyCompliantPolicies; }
166
+ public void setPartiallyCompliantPolicies(Integer partiallyCompliantPolicies) { this.partiallyCompliantPolicies = partiallyCompliantPolicies; }
167
+ public Double getOverallComplianceScore() { return overallComplianceScore; }
168
+ public void setOverallComplianceScore(Double overallComplianceScore) { this.overallComplianceScore = overallComplianceScore; }
169
+ public String getOverallStatus() { return overallStatus; }
170
+ public void setOverallStatus(String overallStatus) { this.overallStatus = overallStatus; }
171
+ public Map<String, Integer> getPolicyTypeBreakdown() { return policyTypeBreakdown; }
172
+ public void setPolicyTypeBreakdown(Map<String, Integer> policyTypeBreakdown) { this.policyTypeBreakdown = policyTypeBreakdown; }
173
+ public Map<String, Integer> getComplianceStatusBreakdown() { return complianceStatusBreakdown; }
174
+ public void setComplianceStatusBreakdown(Map<String, Integer> complianceStatusBreakdown) { this.complianceStatusBreakdown = complianceStatusBreakdown; }
175
+ public List<String> getCriticalIssues() { return criticalIssues; }
176
+ public void setCriticalIssues(List<String> criticalIssues) { this.criticalIssues = criticalIssues; }
177
+ public Instant getLastFullEvaluation() { return lastFullEvaluation; }
178
+ public void setLastFullEvaluation(Instant lastFullEvaluation) { this.lastFullEvaluation = lastFullEvaluation; }
179
+ }
180
+
181
+ /**
182
+ * DTO for policy risk assessment.
183
+ */
184
+ @JsonInclude(JsonInclude.Include.NON_NULL)
185
+ public static class PolicyRiskDTO {
186
+
187
+ private String riskType; // COMPLIANCE, SECURITY, GOVERNANCE, OPERATIONAL
188
+ private String riskLevel; // CRITICAL, HIGH, MEDIUM, LOW
189
+ private String description;
190
+ private String impact;
191
+ private String likelihood;
192
+ private List<String> mitigationActions;
193
+ private String owner;
194
+ private Instant identifiedAt;
195
+
196
+ public PolicyRiskDTO() {}
197
+
198
+ public PolicyRiskDTO(String riskType, String riskLevel, String description) {
199
+ this.riskType = riskType;
200
+ this.riskLevel = riskLevel;
201
+ this.description = description;
202
+ }
203
+
204
+ // Getters and setters
205
+ public String getRiskType() { return riskType; }
206
+ public void setRiskType(String riskType) { this.riskType = riskType; }
207
+ public String getRiskLevel() { return riskLevel; }
208
+ public void setRiskLevel(String riskLevel) { this.riskLevel = riskLevel; }
209
+ public String getDescription() { return description; }
210
+ public void setDescription(String description) { this.description = description; }
211
+ public String getImpact() { return impact; }
212
+ public void setImpact(String impact) { this.impact = impact; }
213
+ public String getLikelihood() { return likelihood; }
214
+ public void setLikelihood(String likelihood) { this.likelihood = likelihood; }
215
+ public List<String> getMitigationActions() { return mitigationActions; }
216
+ public void setMitigationActions(List<String> mitigationActions) { this.mitigationActions = mitigationActions; }
217
+ public String getOwner() { return owner; }
218
+ public void setOwner(String owner) { this.owner = owner; }
219
+ public Instant getIdentifiedAt() { return identifiedAt; }
220
+ public void setIdentifiedAt(Instant identifiedAt) { this.identifiedAt = identifiedAt; }
221
+ }
222
+
223
+ /**
224
+ * DTO for policy recommendations.
225
+ */
226
+ @JsonInclude(JsonInclude.Include.NON_NULL)
227
+ public static class PolicyRecommendationDTO {
228
+
229
+ private String recommendationType; // POLICY_UPDATE, EXEMPTION_REQUEST, REMEDIATION_ACTION
230
+ private String priority; // HIGH, MEDIUM, LOW
231
+ private String title;
232
+ private String description;
233
+ private String actionRequired;
234
+ private String expectedOutcome;
235
+ private Integer estimatedEffort; // in hours
236
+ private String assignedTo;
237
+ private Instant createdAt;
238
+
239
+ public PolicyRecommendationDTO() {}
240
+
241
+ public PolicyRecommendationDTO(String recommendationType, String priority, String title) {
242
+ this.recommendationType = recommendationType;
243
+ this.priority = priority;
244
+ this.title = title;
245
+ }
246
+
247
+ // Getters and setters
248
+ public String getRecommendationType() { return recommendationType; }
249
+ public void setRecommendationType(String recommendationType) { this.recommendationType = recommendationType; }
250
+ public String getPriority() { return priority; }
251
+ public void setPriority(String priority) { this.priority = priority; }
252
+ public String getTitle() { return title; }
253
+ public void setTitle(String title) { this.title = title; }
254
+ public String getDescription() { return description; }
255
+ public void setDescription(String description) { this.description = description; }
256
+ public String getActionRequired() { return actionRequired; }
257
+ public void setActionRequired(String actionRequired) { this.actionRequired = actionRequired; }
258
+ public String getExpectedOutcome() { return expectedOutcome; }
259
+ public void setExpectedOutcome(String expectedOutcome) { this.expectedOutcome = expectedOutcome; }
260
+ public Integer getEstimatedEffort() { return estimatedEffort; }
261
+ public void setEstimatedEffort(Integer estimatedEffort) { this.estimatedEffort = estimatedEffort; }
262
+ public String getAssignedTo() { return assignedTo; }
263
+ public void setAssignedTo(String assignedTo) { this.assignedTo = assignedTo; }
264
+ public Instant getCreatedAt() { return createdAt; }
265
+ public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
266
+ }
267
+
268
+ // Main class getters and setters
269
+ public UUID getAssetId() { return assetId; }
270
+ public void setAssetId(UUID assetId) { this.assetId = assetId; }
271
+ public String getAssetName() { return assetName; }
272
+ public void setAssetName(String assetName) { this.assetName = assetName; }
273
+ public List<PolicyMappingDTO> getApplicablePolicies() { return applicablePolicies; }
274
+ public void setApplicablePolicies(List<PolicyMappingDTO> applicablePolicies) { this.applicablePolicies = applicablePolicies; }
275
+ public List<PolicyMappingDTO> getInheritedPolicies() { return inheritedPolicies; }
276
+ public void setInheritedPolicies(List<PolicyMappingDTO> inheritedPolicies) { this.inheritedPolicies = inheritedPolicies; }
277
+ public PolicyComplianceSummaryDTO getComplianceSummary() { return complianceSummary; }
278
+ public void setComplianceSummary(PolicyComplianceSummaryDTO complianceSummary) { this.complianceSummary = complianceSummary; }
279
+ public List<PolicyRiskDTO> getRiskAssessment() { return riskAssessment; }
280
+ public void setRiskAssessment(List<PolicyRiskDTO> riskAssessment) { this.riskAssessment = riskAssessment; }
281
+ public List<PolicyRecommendationDTO> getRecommendations() { return recommendations; }
282
+ public void setRecommendations(List<PolicyRecommendationDTO> recommendations) { this.recommendations = recommendations; }
283
+ public Instant getLastEvaluated() { return lastEvaluated; }
284
+ public void setLastEvaluated(Instant lastEvaluated) { this.lastEvaluated = lastEvaluated; }
285
+ public String getEvaluationStatus() { return evaluationStatus; }
286
+ public void setEvaluationStatus(String evaluationStatus) { this.evaluationStatus = evaluationStatus; }
287
+ }
src/main/java/com/dalab/catalog/dto/AssetSchemaDTO.java ADDED
@@ -0,0 +1,574 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+ import java.util.UUID;
7
+
8
+ import com.fasterxml.jackson.annotation.JsonInclude;
9
+
10
+ /**
11
+ * Comprehensive DTO for Asset Schema Information.
12
+ * Provides detailed schema structure with field metadata, data types, constraints, and lineage.
13
+ * Priority 2 endpoint implementation for enhanced catalog functionality.
14
+ */
15
+ @JsonInclude(JsonInclude.Include.NON_NULL)
16
+ public class AssetSchemaDTO {
17
+
18
+ private UUID assetId;
19
+ private String assetName;
20
+ private String schemaVersion;
21
+ private SchemaTypeDTO schemaType;
22
+ private List<SchemaFieldDTO> fields;
23
+ private List<SchemaConstraintDTO> constraints;
24
+ private List<SchemaIndexDTO> indexes;
25
+ private SchemaStatisticsDTO statistics;
26
+ private SchemaLineageDTO lineage;
27
+ private SchemaEvolutionDTO evolution;
28
+ private Map<String, Object> metadata;
29
+ private Instant lastUpdated;
30
+ private Instant lastAnalyzed;
31
+ private String source;
32
+
33
+ /**
34
+ * Default constructor for AssetSchemaDTO.
35
+ */
36
+ public AssetSchemaDTO() {}
37
+
38
+ /**
39
+ * Constructor with basic schema information.
40
+ */
41
+ public AssetSchemaDTO(UUID assetId, String assetName, String schemaVersion, SchemaTypeDTO schemaType) {
42
+ this.assetId = assetId;
43
+ this.assetName = assetName;
44
+ this.schemaVersion = schemaVersion;
45
+ this.schemaType = schemaType;
46
+ }
47
+
48
+ /**
49
+ * DTO for Schema Type Information.
50
+ */
51
+ @JsonInclude(JsonInclude.Include.NON_NULL)
52
+ public static class SchemaTypeDTO {
53
+ private String type; // TABLE, VIEW, FILE, API, STREAM, DOCUMENT
54
+ private String format; // CSV, JSON, PARQUET, AVRO, XML, etc.
55
+ private String encoding; // UTF-8, ASCII, etc.
56
+ private Map<String, Object> formatSpecificProperties;
57
+
58
+ public SchemaTypeDTO() {}
59
+
60
+ public SchemaTypeDTO(String type, String format) {
61
+ this.type = type;
62
+ this.format = format;
63
+ }
64
+
65
+ // Getters and setters
66
+ public String getType() { return type; }
67
+ public void setType(String type) { this.type = type; }
68
+ public String getFormat() { return format; }
69
+ public void setFormat(String format) { this.format = format; }
70
+ public String getEncoding() { return encoding; }
71
+ public void setEncoding(String encoding) { this.encoding = encoding; }
72
+ public Map<String, Object> getFormatSpecificProperties() { return formatSpecificProperties; }
73
+ public void setFormatSpecificProperties(Map<String, Object> formatSpecificProperties) {
74
+ this.formatSpecificProperties = formatSpecificProperties;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * DTO for individual schema fields with comprehensive metadata.
80
+ */
81
+ @JsonInclude(JsonInclude.Include.NON_NULL)
82
+ public static class SchemaFieldDTO {
83
+ private String fieldName;
84
+ private String dataType;
85
+ private String logicalType;
86
+ private boolean nullable;
87
+ private boolean primaryKey;
88
+ private boolean foreignKey;
89
+ private String defaultValue;
90
+ private String description;
91
+ private List<String> tags;
92
+ private FieldStatisticsDTO statistics;
93
+ private FieldConstraintsDTO constraints;
94
+ private Integer ordinalPosition;
95
+ private String classification; // PII, SENSITIVE, PUBLIC, etc.
96
+ private Map<String, Object> properties;
97
+
98
+ public SchemaFieldDTO() {}
99
+
100
+ public SchemaFieldDTO(String fieldName, String dataType, boolean nullable) {
101
+ this.fieldName = fieldName;
102
+ this.dataType = dataType;
103
+ this.nullable = nullable;
104
+ }
105
+
106
+ // Getters and setters
107
+ public String getFieldName() { return fieldName; }
108
+ public void setFieldName(String fieldName) { this.fieldName = fieldName; }
109
+ public String getDataType() { return dataType; }
110
+ public void setDataType(String dataType) { this.dataType = dataType; }
111
+ public String getLogicalType() { return logicalType; }
112
+ public void setLogicalType(String logicalType) { this.logicalType = logicalType; }
113
+ public boolean isNullable() { return nullable; }
114
+ public void setNullable(boolean nullable) { this.nullable = nullable; }
115
+ public boolean isPrimaryKey() { return primaryKey; }
116
+ public void setPrimaryKey(boolean primaryKey) { this.primaryKey = primaryKey; }
117
+ public boolean isForeignKey() { return foreignKey; }
118
+ public void setForeignKey(boolean foreignKey) { this.foreignKey = foreignKey; }
119
+ public String getDefaultValue() { return defaultValue; }
120
+ public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; }
121
+ public String getDescription() { return description; }
122
+ public void setDescription(String description) { this.description = description; }
123
+ public List<String> getTags() { return tags; }
124
+ public void setTags(List<String> tags) { this.tags = tags; }
125
+ public FieldStatisticsDTO getStatistics() { return statistics; }
126
+ public void setStatistics(FieldStatisticsDTO statistics) { this.statistics = statistics; }
127
+ public FieldConstraintsDTO getConstraints() { return constraints; }
128
+ public void setConstraints(FieldConstraintsDTO constraints) { this.constraints = constraints; }
129
+ public Integer getOrdinalPosition() { return ordinalPosition; }
130
+ public void setOrdinalPosition(Integer ordinalPosition) { this.ordinalPosition = ordinalPosition; }
131
+ public String getClassification() { return classification; }
132
+ public void setClassification(String classification) { this.classification = classification; }
133
+ public Map<String, Object> getProperties() { return properties; }
134
+ public void setProperties(Map<String, Object> properties) { this.properties = properties; }
135
+ }
136
+
137
+ /**
138
+ * DTO for field-level statistics.
139
+ */
140
+ @JsonInclude(JsonInclude.Include.NON_NULL)
141
+ public static class FieldStatisticsDTO {
142
+ private Long distinctCount;
143
+ private Long nullCount;
144
+ private Double fillRate;
145
+ private String minValue;
146
+ private String maxValue;
147
+ private Double avgLength;
148
+ private Double maxLength;
149
+ private List<String> topValues;
150
+ private Instant lastAnalyzed;
151
+
152
+ public FieldStatisticsDTO() {}
153
+
154
+ // Getters and setters
155
+ public Long getDistinctCount() { return distinctCount; }
156
+ public void setDistinctCount(Long distinctCount) { this.distinctCount = distinctCount; }
157
+ public Long getNullCount() { return nullCount; }
158
+ public void setNullCount(Long nullCount) { this.nullCount = nullCount; }
159
+ public Double getFillRate() { return fillRate; }
160
+ public void setFillRate(Double fillRate) { this.fillRate = fillRate; }
161
+ public String getMinValue() { return minValue; }
162
+ public void setMinValue(String minValue) { this.minValue = minValue; }
163
+ public String getMaxValue() { return maxValue; }
164
+ public void setMaxValue(String maxValue) { this.maxValue = maxValue; }
165
+ public Double getAvgLength() { return avgLength; }
166
+ public void setAvgLength(Double avgLength) { this.avgLength = avgLength; }
167
+ public Double getMaxLength() { return maxLength; }
168
+ public void setMaxLength(Double maxLength) { this.maxLength = maxLength; }
169
+ public List<String> getTopValues() { return topValues; }
170
+ public void setTopValues(List<String> topValues) { this.topValues = topValues; }
171
+ public Instant getLastAnalyzed() { return lastAnalyzed; }
172
+ public void setLastAnalyzed(Instant lastAnalyzed) { this.lastAnalyzed = lastAnalyzed; }
173
+ }
174
+
175
+ /**
176
+ * DTO for field-level constraints.
177
+ */
178
+ @JsonInclude(JsonInclude.Include.NON_NULL)
179
+ public static class FieldConstraintsDTO {
180
+ private String pattern; // Regex pattern for validation
181
+ private Integer minLength;
182
+ private Integer maxLength;
183
+ private String minValue;
184
+ private String maxValue;
185
+ private List<String> enumValues;
186
+ private String format; // EMAIL, PHONE, URL, etc.
187
+ private Map<String, Object> customConstraints;
188
+
189
+ public FieldConstraintsDTO() {}
190
+
191
+ // Getters and setters
192
+ public String getPattern() { return pattern; }
193
+ public void setPattern(String pattern) { this.pattern = pattern; }
194
+ public Integer getMinLength() { return minLength; }
195
+ public void setMinLength(Integer minLength) { this.minLength = minLength; }
196
+ public Integer getMaxLength() { return maxLength; }
197
+ public void setMaxLength(Integer maxLength) { this.maxLength = maxLength; }
198
+ public String getMinValue() { return minValue; }
199
+ public void setMinValue(String minValue) { this.minValue = minValue; }
200
+ public String getMaxValue() { return maxValue; }
201
+ public void setMaxValue(String maxValue) { this.maxValue = maxValue; }
202
+ public List<String> getEnumValues() { return enumValues; }
203
+ public void setEnumValues(List<String> enumValues) { this.enumValues = enumValues; }
204
+ public String getFormat() { return format; }
205
+ public void setFormat(String format) { this.format = format; }
206
+ public Map<String, Object> getCustomConstraints() { return customConstraints; }
207
+ public void setCustomConstraints(Map<String, Object> customConstraints) {
208
+ this.customConstraints = customConstraints;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * DTO for schema-level constraints.
214
+ */
215
+ @JsonInclude(JsonInclude.Include.NON_NULL)
216
+ public static class SchemaConstraintDTO {
217
+ private String constraintName;
218
+ private String constraintType; // PRIMARY_KEY, FOREIGN_KEY, UNIQUE, CHECK, NOT_NULL
219
+ private List<String> columnNames;
220
+ private String referencedTable;
221
+ private List<String> referencedColumns;
222
+ private String checkExpression;
223
+ private boolean enforced;
224
+
225
+ public SchemaConstraintDTO() {}
226
+
227
+ public SchemaConstraintDTO(String constraintName, String constraintType) {
228
+ this.constraintName = constraintName;
229
+ this.constraintType = constraintType;
230
+ }
231
+
232
+ // Getters and setters
233
+ public String getConstraintName() { return constraintName; }
234
+ public void setConstraintName(String constraintName) { this.constraintName = constraintName; }
235
+ public String getConstraintType() { return constraintType; }
236
+ public void setConstraintType(String constraintType) { this.constraintType = constraintType; }
237
+ public List<String> getColumnNames() { return columnNames; }
238
+ public void setColumnNames(List<String> columnNames) { this.columnNames = columnNames; }
239
+ public String getReferencedTable() { return referencedTable; }
240
+ public void setReferencedTable(String referencedTable) { this.referencedTable = referencedTable; }
241
+ public List<String> getReferencedColumns() { return referencedColumns; }
242
+ public void setReferencedColumns(List<String> referencedColumns) { this.referencedColumns = referencedColumns; }
243
+ public String getCheckExpression() { return checkExpression; }
244
+ public void setCheckExpression(String checkExpression) { this.checkExpression = checkExpression; }
245
+ public boolean isEnforced() { return enforced; }
246
+ public void setEnforced(boolean enforced) { this.enforced = enforced; }
247
+ }
248
+
249
+ /**
250
+ * DTO for schema indexes.
251
+ */
252
+ @JsonInclude(JsonInclude.Include.NON_NULL)
253
+ public static class SchemaIndexDTO {
254
+ private String indexName;
255
+ private String indexType; // BTREE, HASH, BITMAP, CLUSTERED, etc.
256
+ private List<String> columnNames;
257
+ private boolean unique;
258
+ private boolean primary;
259
+ private Long cardinality;
260
+ private Map<String, Object> properties;
261
+
262
+ public SchemaIndexDTO() {}
263
+
264
+ public SchemaIndexDTO(String indexName, String indexType, boolean unique) {
265
+ this.indexName = indexName;
266
+ this.indexType = indexType;
267
+ this.unique = unique;
268
+ }
269
+
270
+ // Getters and setters
271
+ public String getIndexName() { return indexName; }
272
+ public void setIndexName(String indexName) { this.indexName = indexName; }
273
+ public String getIndexType() { return indexType; }
274
+ public void setIndexType(String indexType) { this.indexType = indexType; }
275
+ public List<String> getColumnNames() { return columnNames; }
276
+ public void setColumnNames(List<String> columnNames) { this.columnNames = columnNames; }
277
+ public boolean isUnique() { return unique; }
278
+ public void setUnique(boolean unique) { this.unique = unique; }
279
+ public boolean isPrimary() { return primary; }
280
+ public void setPrimary(boolean primary) { this.primary = primary; }
281
+ public Long getCardinality() { return cardinality; }
282
+ public void setCardinality(Long cardinality) { this.cardinality = cardinality; }
283
+ public Map<String, Object> getProperties() { return properties; }
284
+ public void setProperties(Map<String, Object> properties) { this.properties = properties; }
285
+ }
286
+
287
+ /**
288
+ * DTO for schema-level statistics.
289
+ */
290
+ @JsonInclude(JsonInclude.Include.NON_NULL)
291
+ public static class SchemaStatisticsDTO {
292
+ private Long totalRows;
293
+ private Long totalColumns;
294
+ private Long totalSize;
295
+ private Double compressionRatio;
296
+ private Instant lastUpdated;
297
+ private Instant lastAnalyzed;
298
+ private Map<String, Long> dataTypeCounts;
299
+ private Double schemaComplexity;
300
+ private Integer schemaDepth;
301
+
302
+ public SchemaStatisticsDTO() {}
303
+
304
+ // Getters and setters
305
+ public Long getTotalRows() { return totalRows; }
306
+ public void setTotalRows(Long totalRows) { this.totalRows = totalRows; }
307
+ public Long getTotalColumns() { return totalColumns; }
308
+ public void setTotalColumns(Long totalColumns) { this.totalColumns = totalColumns; }
309
+ public Long getTotalSize() { return totalSize; }
310
+ public void setTotalSize(Long totalSize) { this.totalSize = totalSize; }
311
+ public Double getCompressionRatio() { return compressionRatio; }
312
+ public void setCompressionRatio(Double compressionRatio) { this.compressionRatio = compressionRatio; }
313
+ public Instant getLastUpdated() { return lastUpdated; }
314
+ public void setLastUpdated(Instant lastUpdated) { this.lastUpdated = lastUpdated; }
315
+ public Instant getLastAnalyzed() { return lastAnalyzed; }
316
+ public void setLastAnalyzed(Instant lastAnalyzed) { this.lastAnalyzed = lastAnalyzed; }
317
+ public Map<String, Long> getDataTypeCounts() { return dataTypeCounts; }
318
+ public void setDataTypeCounts(Map<String, Long> dataTypeCounts) { this.dataTypeCounts = dataTypeCounts; }
319
+ public Double getSchemaComplexity() { return schemaComplexity; }
320
+ public void setSchemaComplexity(Double schemaComplexity) { this.schemaComplexity = schemaComplexity; }
321
+ public Integer getSchemaDepth() { return schemaDepth; }
322
+ public void setSchemaDepth(Integer schemaDepth) { this.schemaDepth = schemaDepth; }
323
+ }
324
+
325
+ /**
326
+ * DTO for schema lineage information.
327
+ */
328
+ @JsonInclude(JsonInclude.Include.NON_NULL)
329
+ public static class SchemaLineageDTO {
330
+ private List<SchemaRelationshipDTO> upstream;
331
+ private List<SchemaRelationshipDTO> downstream;
332
+ private List<SchemaTransformationDTO> transformations;
333
+ private Integer lineageDepth;
334
+ private Instant lineageLastUpdated;
335
+
336
+ public SchemaLineageDTO() {}
337
+
338
+ // Getters and setters
339
+ public List<SchemaRelationshipDTO> getUpstream() { return upstream; }
340
+ public void setUpstream(List<SchemaRelationshipDTO> upstream) { this.upstream = upstream; }
341
+ public List<SchemaRelationshipDTO> getDownstream() { return downstream; }
342
+ public void setDownstream(List<SchemaRelationshipDTO> downstream) { this.downstream = downstream; }
343
+ public List<SchemaTransformationDTO> getTransformations() { return transformations; }
344
+ public void setTransformations(List<SchemaTransformationDTO> transformations) {
345
+ this.transformations = transformations;
346
+ }
347
+ public Integer getLineageDepth() { return lineageDepth; }
348
+ public void setLineageDepth(Integer lineageDepth) { this.lineageDepth = lineageDepth; }
349
+ public Instant getLineageLastUpdated() { return lineageLastUpdated; }
350
+ public void setLineageLastUpdated(Instant lineageLastUpdated) { this.lineageLastUpdated = lineageLastUpdated; }
351
+ }
352
+
353
+ /**
354
+ * DTO for schema relationships in lineage.
355
+ */
356
+ @JsonInclude(JsonInclude.Include.NON_NULL)
357
+ public static class SchemaRelationshipDTO {
358
+ private UUID relatedAssetId;
359
+ private String relatedAssetName;
360
+ private String relationshipType; // DERIVED_FROM, COPIED_TO, JOINED_WITH, etc.
361
+ private List<FieldMappingDTO> fieldMappings;
362
+ private String transformationType;
363
+ private Instant relationshipCreated;
364
+
365
+ public SchemaRelationshipDTO() {}
366
+
367
+ // Getters and setters
368
+ public UUID getRelatedAssetId() { return relatedAssetId; }
369
+ public void setRelatedAssetId(UUID relatedAssetId) { this.relatedAssetId = relatedAssetId; }
370
+ public String getRelatedAssetName() { return relatedAssetName; }
371
+ public void setRelatedAssetName(String relatedAssetName) { this.relatedAssetName = relatedAssetName; }
372
+ public String getRelationshipType() { return relationshipType; }
373
+ public void setRelationshipType(String relationshipType) { this.relationshipType = relationshipType; }
374
+ public List<FieldMappingDTO> getFieldMappings() { return fieldMappings; }
375
+ public void setFieldMappings(List<FieldMappingDTO> fieldMappings) { this.fieldMappings = fieldMappings; }
376
+ public String getTransformationType() { return transformationType; }
377
+ public void setTransformationType(String transformationType) { this.transformationType = transformationType; }
378
+ public Instant getRelationshipCreated() { return relationshipCreated; }
379
+ public void setRelationshipCreated(Instant relationshipCreated) { this.relationshipCreated = relationshipCreated; }
380
+ }
381
+
382
+ /**
383
+ * DTO for field mappings in schema relationships.
384
+ */
385
+ @JsonInclude(JsonInclude.Include.NON_NULL)
386
+ public static class FieldMappingDTO {
387
+ private String sourceField;
388
+ private String targetField;
389
+ private String mappingType; // DIRECT, TRANSFORMED, CALCULATED, etc.
390
+ private String transformation;
391
+ private Double confidence;
392
+
393
+ public FieldMappingDTO() {}
394
+
395
+ public FieldMappingDTO(String sourceField, String targetField, String mappingType) {
396
+ this.sourceField = sourceField;
397
+ this.targetField = targetField;
398
+ this.mappingType = mappingType;
399
+ }
400
+
401
+ // Getters and setters
402
+ public String getSourceField() { return sourceField; }
403
+ public void setSourceField(String sourceField) { this.sourceField = sourceField; }
404
+ public String getTargetField() { return targetField; }
405
+ public void setTargetField(String targetField) { this.targetField = targetField; }
406
+ public String getMappingType() { return mappingType; }
407
+ public void setMappingType(String mappingType) { this.mappingType = mappingType; }
408
+ public String getTransformation() { return transformation; }
409
+ public void setTransformation(String transformation) { this.transformation = transformation; }
410
+ public Double getConfidence() { return confidence; }
411
+ public void setConfidence(Double confidence) { this.confidence = confidence; }
412
+ }
413
+
414
+ /**
415
+ * DTO for schema transformations.
416
+ */
417
+ @JsonInclude(JsonInclude.Include.NON_NULL)
418
+ public static class SchemaTransformationDTO {
419
+ private String transformationId;
420
+ private String transformationType; // ETL, ELT, STREAM_PROCESSING, etc.
421
+ private String transformationLogic;
422
+ private List<String> inputFields;
423
+ private List<String> outputFields;
424
+ private Map<String, Object> parameters;
425
+ private Instant executedAt;
426
+
427
+ public SchemaTransformationDTO() {}
428
+
429
+ // Getters and setters
430
+ public String getTransformationId() { return transformationId; }
431
+ public void setTransformationId(String transformationId) { this.transformationId = transformationId; }
432
+ public String getTransformationType() { return transformationType; }
433
+ public void setTransformationType(String transformationType) { this.transformationType = transformationType; }
434
+ public String getTransformationLogic() { return transformationLogic; }
435
+ public void setTransformationLogic(String transformationLogic) { this.transformationLogic = transformationLogic; }
436
+ public List<String> getInputFields() { return inputFields; }
437
+ public void setInputFields(List<String> inputFields) { this.inputFields = inputFields; }
438
+ public List<String> getOutputFields() { return outputFields; }
439
+ public void setOutputFields(List<String> outputFields) { this.outputFields = outputFields; }
440
+ public Map<String, Object> getParameters() { return parameters; }
441
+ public void setParameters(Map<String, Object> parameters) { this.parameters = parameters; }
442
+ public Instant getExecutedAt() { return executedAt; }
443
+ public void setExecutedAt(Instant executedAt) { this.executedAt = executedAt; }
444
+ }
445
+
446
+ /**
447
+ * DTO for schema evolution tracking.
448
+ */
449
+ @JsonInclude(JsonInclude.Include.NON_NULL)
450
+ public static class SchemaEvolutionDTO {
451
+ private List<SchemaVersionDTO> versions;
452
+ private List<SchemaChangeDTO> recentChanges;
453
+ private Integer totalVersions;
454
+ private Instant firstVersion;
455
+ private Instant lastChange;
456
+
457
+ public SchemaEvolutionDTO() {}
458
+
459
+ // Getters and setters
460
+ public List<SchemaVersionDTO> getVersions() { return versions; }
461
+ public void setVersions(List<SchemaVersionDTO> versions) { this.versions = versions; }
462
+ public List<SchemaChangeDTO> getRecentChanges() { return recentChanges; }
463
+ public void setRecentChanges(List<SchemaChangeDTO> recentChanges) { this.recentChanges = recentChanges; }
464
+ public Integer getTotalVersions() { return totalVersions; }
465
+ public void setTotalVersions(Integer totalVersions) { this.totalVersions = totalVersions; }
466
+ public Instant getFirstVersion() { return firstVersion; }
467
+ public void setFirstVersion(Instant firstVersion) { this.firstVersion = firstVersion; }
468
+ public Instant getLastChange() { return lastChange; }
469
+ public void setLastChange(Instant lastChange) { this.lastChange = lastChange; }
470
+ }
471
+
472
+ /**
473
+ * DTO for schema versions.
474
+ */
475
+ @JsonInclude(JsonInclude.Include.NON_NULL)
476
+ public static class SchemaVersionDTO {
477
+ private String version;
478
+ private Instant createdAt;
479
+ private String createdBy;
480
+ private String changeDescription;
481
+ private Integer fieldCount;
482
+ private List<String> changedFields;
483
+
484
+ public SchemaVersionDTO() {}
485
+
486
+ public SchemaVersionDTO(String version, Instant createdAt, String createdBy) {
487
+ this.version = version;
488
+ this.createdAt = createdAt;
489
+ this.createdBy = createdBy;
490
+ }
491
+
492
+ // Getters and setters
493
+ public String getVersion() { return version; }
494
+ public void setVersion(String version) { this.version = version; }
495
+ public Instant getCreatedAt() { return createdAt; }
496
+ public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
497
+ public String getCreatedBy() { return createdBy; }
498
+ public void setCreatedBy(String createdBy) { this.createdBy = createdBy; }
499
+ public String getChangeDescription() { return changeDescription; }
500
+ public void setChangeDescription(String changeDescription) { this.changeDescription = changeDescription; }
501
+ public Integer getFieldCount() { return fieldCount; }
502
+ public void setFieldCount(Integer fieldCount) { this.fieldCount = fieldCount; }
503
+ public List<String> getChangedFields() { return changedFields; }
504
+ public void setChangedFields(List<String> changedFields) { this.changedFields = changedFields; }
505
+ }
506
+
507
+ /**
508
+ * DTO for schema changes.
509
+ */
510
+ @JsonInclude(JsonInclude.Include.NON_NULL)
511
+ public static class SchemaChangeDTO {
512
+ private String changeType; // FIELD_ADDED, FIELD_REMOVED, FIELD_MODIFIED, CONSTRAINT_ADDED, etc.
513
+ private String fieldName;
514
+ private String oldValue;
515
+ private String newValue;
516
+ private Instant changeTimestamp;
517
+ private String changedBy;
518
+ private String changeReason;
519
+
520
+ public SchemaChangeDTO() {}
521
+
522
+ public SchemaChangeDTO(String changeType, String fieldName, Instant changeTimestamp) {
523
+ this.changeType = changeType;
524
+ this.fieldName = fieldName;
525
+ this.changeTimestamp = changeTimestamp;
526
+ }
527
+
528
+ // Getters and setters
529
+ public String getChangeType() { return changeType; }
530
+ public void setChangeType(String changeType) { this.changeType = changeType; }
531
+ public String getFieldName() { return fieldName; }
532
+ public void setFieldName(String fieldName) { this.fieldName = fieldName; }
533
+ public String getOldValue() { return oldValue; }
534
+ public void setOldValue(String oldValue) { this.oldValue = oldValue; }
535
+ public String getNewValue() { return newValue; }
536
+ public void setNewValue(String newValue) { this.newValue = newValue; }
537
+ public Instant getChangeTimestamp() { return changeTimestamp; }
538
+ public void setChangeTimestamp(Instant changeTimestamp) { this.changeTimestamp = changeTimestamp; }
539
+ public String getChangedBy() { return changedBy; }
540
+ public void setChangedBy(String changedBy) { this.changedBy = changedBy; }
541
+ public String getChangeReason() { return changeReason; }
542
+ public void setChangeReason(String changeReason) { this.changeReason = changeReason; }
543
+ }
544
+
545
+ // Main class getters and setters
546
+ public UUID getAssetId() { return assetId; }
547
+ public void setAssetId(UUID assetId) { this.assetId = assetId; }
548
+ public String getAssetName() { return assetName; }
549
+ public void setAssetName(String assetName) { this.assetName = assetName; }
550
+ public String getSchemaVersion() { return schemaVersion; }
551
+ public void setSchemaVersion(String schemaVersion) { this.schemaVersion = schemaVersion; }
552
+ public SchemaTypeDTO getSchemaType() { return schemaType; }
553
+ public void setSchemaType(SchemaTypeDTO schemaType) { this.schemaType = schemaType; }
554
+ public List<SchemaFieldDTO> getFields() { return fields; }
555
+ public void setFields(List<SchemaFieldDTO> fields) { this.fields = fields; }
556
+ public List<SchemaConstraintDTO> getConstraints() { return constraints; }
557
+ public void setConstraints(List<SchemaConstraintDTO> constraints) { this.constraints = constraints; }
558
+ public List<SchemaIndexDTO> getIndexes() { return indexes; }
559
+ public void setIndexes(List<SchemaIndexDTO> indexes) { this.indexes = indexes; }
560
+ public SchemaStatisticsDTO getStatistics() { return statistics; }
561
+ public void setStatistics(SchemaStatisticsDTO statistics) { this.statistics = statistics; }
562
+ public SchemaLineageDTO getLineage() { return lineage; }
563
+ public void setLineage(SchemaLineageDTO lineage) { this.lineage = lineage; }
564
+ public SchemaEvolutionDTO getEvolution() { return evolution; }
565
+ public void setEvolution(SchemaEvolutionDTO evolution) { this.evolution = evolution; }
566
+ public Map<String, Object> getMetadata() { return metadata; }
567
+ public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
568
+ public Instant getLastUpdated() { return lastUpdated; }
569
+ public void setLastUpdated(Instant lastUpdated) { this.lastUpdated = lastUpdated; }
570
+ public Instant getLastAnalyzed() { return lastAnalyzed; }
571
+ public void setLastAnalyzed(Instant lastAnalyzed) { this.lastAnalyzed = lastAnalyzed; }
572
+ public String getSource() { return source; }
573
+ public void setSource(String source) { this.source = source; }
574
+ }
src/main/java/com/dalab/catalog/dto/AssetUsageAnalyticsDTO.java ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+ import java.util.UUID;
7
+
8
+ import com.fasterxml.jackson.annotation.JsonInclude;
9
+
10
+ /**
11
+ * DTO for comprehensive asset usage analytics.
12
+ * Provides insights into asset access patterns, user behavior, performance metrics,
13
+ * and recommendations for optimization.
14
+ */
15
+ @JsonInclude(JsonInclude.Include.NON_NULL)
16
+ public class AssetUsageAnalyticsDTO {
17
+
18
+ private UsageMetricsDTO usageMetrics;
19
+ private UserAnalyticsDTO userAnalytics;
20
+ private AccessPatternsDTO accessPatterns;
21
+ private PerformanceMetricsDTO performanceMetrics;
22
+ private List<PopularQueryDTO> popularQueries;
23
+ private List<RecommendationDTO> recommendations;
24
+
25
+ // Constructor
26
+ public AssetUsageAnalyticsDTO() {}
27
+
28
+ // Getters and Setters
29
+ public UsageMetricsDTO getUsageMetrics() {
30
+ return usageMetrics;
31
+ }
32
+
33
+ public void setUsageMetrics(UsageMetricsDTO usageMetrics) {
34
+ this.usageMetrics = usageMetrics;
35
+ }
36
+
37
+ public UserAnalyticsDTO getUserAnalytics() {
38
+ return userAnalytics;
39
+ }
40
+
41
+ public void setUserAnalytics(UserAnalyticsDTO userAnalytics) {
42
+ this.userAnalytics = userAnalytics;
43
+ }
44
+
45
+ public AccessPatternsDTO getAccessPatterns() {
46
+ return accessPatterns;
47
+ }
48
+
49
+ public void setAccessPatterns(AccessPatternsDTO accessPatterns) {
50
+ this.accessPatterns = accessPatterns;
51
+ }
52
+
53
+ public PerformanceMetricsDTO getPerformanceMetrics() {
54
+ return performanceMetrics;
55
+ }
56
+
57
+ public void setPerformanceMetrics(PerformanceMetricsDTO performanceMetrics) {
58
+ this.performanceMetrics = performanceMetrics;
59
+ }
60
+
61
+ public List<PopularQueryDTO> getPopularQueries() {
62
+ return popularQueries;
63
+ }
64
+
65
+ public void setPopularQueries(List<PopularQueryDTO> popularQueries) {
66
+ this.popularQueries = popularQueries;
67
+ }
68
+
69
+ public List<RecommendationDTO> getRecommendations() {
70
+ return recommendations;
71
+ }
72
+
73
+ public void setRecommendations(List<RecommendationDTO> recommendations) {
74
+ this.recommendations = recommendations;
75
+ }
76
+
77
+ /**
78
+ * Usage metrics showing overall asset access statistics.
79
+ */
80
+ public static class UsageMetricsDTO {
81
+ private Long totalAccesses;
82
+ private Integer uniqueUsers;
83
+ private Double avgAccessesPerDay;
84
+ private Instant lastAccessed;
85
+ private Integer peakUsageHour;
86
+ private String usageTrend; // INCREASING, DECREASING, STABLE
87
+
88
+ public UsageMetricsDTO() {}
89
+
90
+ public Long getTotalAccesses() {
91
+ return totalAccesses;
92
+ }
93
+
94
+ public void setTotalAccesses(Long totalAccesses) {
95
+ this.totalAccesses = totalAccesses;
96
+ }
97
+
98
+ public Integer getUniqueUsers() {
99
+ return uniqueUsers;
100
+ }
101
+
102
+ public void setUniqueUsers(Integer uniqueUsers) {
103
+ this.uniqueUsers = uniqueUsers;
104
+ }
105
+
106
+ public Double getAvgAccessesPerDay() {
107
+ return avgAccessesPerDay;
108
+ }
109
+
110
+ public void setAvgAccessesPerDay(Double avgAccessesPerDay) {
111
+ this.avgAccessesPerDay = avgAccessesPerDay;
112
+ }
113
+
114
+ public Instant getLastAccessed() {
115
+ return lastAccessed;
116
+ }
117
+
118
+ public void setLastAccessed(Instant lastAccessed) {
119
+ this.lastAccessed = lastAccessed;
120
+ }
121
+
122
+ public Integer getPeakUsageHour() {
123
+ return peakUsageHour;
124
+ }
125
+
126
+ public void setPeakUsageHour(Integer peakUsageHour) {
127
+ this.peakUsageHour = peakUsageHour;
128
+ }
129
+
130
+ public String getUsageTrend() {
131
+ return usageTrend;
132
+ }
133
+
134
+ public void setUsageTrend(String usageTrend) {
135
+ this.usageTrend = usageTrend;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * User analytics showing who is accessing the asset and how.
141
+ */
142
+ public static class UserAnalyticsDTO {
143
+ private List<TopUserDTO> topUsers;
144
+ private Map<String, Double> departmentBreakdown;
145
+
146
+ public UserAnalyticsDTO() {}
147
+
148
+ public List<TopUserDTO> getTopUsers() {
149
+ return topUsers;
150
+ }
151
+
152
+ public void setTopUsers(List<TopUserDTO> topUsers) {
153
+ this.topUsers = topUsers;
154
+ }
155
+
156
+ public Map<String, Double> getDepartmentBreakdown() {
157
+ return departmentBreakdown;
158
+ }
159
+
160
+ public void setDepartmentBreakdown(Map<String, Double> departmentBreakdown) {
161
+ this.departmentBreakdown = departmentBreakdown;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Individual user access information.
167
+ */
168
+ public static class TopUserDTO {
169
+ private UUID userId;
170
+ private String username;
171
+ private Integer accessCount;
172
+ private Instant lastAccess;
173
+ private String accessPattern; // REGULAR, OCCASIONAL, HEAVY
174
+
175
+ public TopUserDTO() {}
176
+
177
+ public TopUserDTO(UUID userId, String username, Integer accessCount, Instant lastAccess, String accessPattern) {
178
+ this.userId = userId;
179
+ this.username = username;
180
+ this.accessCount = accessCount;
181
+ this.lastAccess = lastAccess;
182
+ this.accessPattern = accessPattern;
183
+ }
184
+
185
+ public UUID getUserId() {
186
+ return userId;
187
+ }
188
+
189
+ public void setUserId(UUID userId) {
190
+ this.userId = userId;
191
+ }
192
+
193
+ public String getUsername() {
194
+ return username;
195
+ }
196
+
197
+ public void setUsername(String username) {
198
+ this.username = username;
199
+ }
200
+
201
+ public Integer getAccessCount() {
202
+ return accessCount;
203
+ }
204
+
205
+ public void setAccessCount(Integer accessCount) {
206
+ this.accessCount = accessCount;
207
+ }
208
+
209
+ public Instant getLastAccess() {
210
+ return lastAccess;
211
+ }
212
+
213
+ public void setLastAccess(Instant lastAccess) {
214
+ this.lastAccess = lastAccess;
215
+ }
216
+
217
+ public String getAccessPattern() {
218
+ return accessPattern;
219
+ }
220
+
221
+ public void setAccessPattern(String accessPattern) {
222
+ this.accessPattern = accessPattern;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Access patterns showing temporal usage distribution.
228
+ */
229
+ public static class AccessPatternsDTO {
230
+ private List<Integer> hourlyDistribution; // 24 hour array
231
+ private List<Integer> weeklyDistribution; // 7 day array
232
+ private List<MonthlyTrendDTO> monthlyTrend;
233
+
234
+ public AccessPatternsDTO() {}
235
+
236
+ public List<Integer> getHourlyDistribution() {
237
+ return hourlyDistribution;
238
+ }
239
+
240
+ public void setHourlyDistribution(List<Integer> hourlyDistribution) {
241
+ this.hourlyDistribution = hourlyDistribution;
242
+ }
243
+
244
+ public List<Integer> getWeeklyDistribution() {
245
+ return weeklyDistribution;
246
+ }
247
+
248
+ public void setWeeklyDistribution(List<Integer> weeklyDistribution) {
249
+ this.weeklyDistribution = weeklyDistribution;
250
+ }
251
+
252
+ public List<MonthlyTrendDTO> getMonthlyTrend() {
253
+ return monthlyTrend;
254
+ }
255
+
256
+ public void setMonthlyTrend(List<MonthlyTrendDTO> monthlyTrend) {
257
+ this.monthlyTrend = monthlyTrend;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Monthly usage trend data.
263
+ */
264
+ public static class MonthlyTrendDTO {
265
+ private String month; // YYYY-MM format
266
+ private Integer totalAccesses;
267
+ private Integer uniqueUsers;
268
+
269
+ public MonthlyTrendDTO() {}
270
+
271
+ public MonthlyTrendDTO(String month, Integer totalAccesses, Integer uniqueUsers) {
272
+ this.month = month;
273
+ this.totalAccesses = totalAccesses;
274
+ this.uniqueUsers = uniqueUsers;
275
+ }
276
+
277
+ public String getMonth() {
278
+ return month;
279
+ }
280
+
281
+ public void setMonth(String month) {
282
+ this.month = month;
283
+ }
284
+
285
+ public Integer getTotalAccesses() {
286
+ return totalAccesses;
287
+ }
288
+
289
+ public void setTotalAccesses(Integer totalAccesses) {
290
+ this.totalAccesses = totalAccesses;
291
+ }
292
+
293
+ public Integer getUniqueUsers() {
294
+ return uniqueUsers;
295
+ }
296
+
297
+ public void setUniqueUsers(Integer uniqueUsers) {
298
+ this.uniqueUsers = uniqueUsers;
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Performance metrics for asset access.
304
+ */
305
+ public static class PerformanceMetricsDTO {
306
+ private String avgQueryTime;
307
+ private String slowestQuery;
308
+ private String fastestQuery;
309
+ private Double timeoutRate;
310
+ private Double errorRate;
311
+
312
+ public PerformanceMetricsDTO() {}
313
+
314
+ public String getAvgQueryTime() {
315
+ return avgQueryTime;
316
+ }
317
+
318
+ public void setAvgQueryTime(String avgQueryTime) {
319
+ this.avgQueryTime = avgQueryTime;
320
+ }
321
+
322
+ public String getSlowestQuery() {
323
+ return slowestQuery;
324
+ }
325
+
326
+ public void setSlowestQuery(String slowestQuery) {
327
+ this.slowestQuery = slowestQuery;
328
+ }
329
+
330
+ public String getFastestQuery() {
331
+ return fastestQuery;
332
+ }
333
+
334
+ public void setFastestQuery(String fastestQuery) {
335
+ this.fastestQuery = fastestQuery;
336
+ }
337
+
338
+ public Double getTimeoutRate() {
339
+ return timeoutRate;
340
+ }
341
+
342
+ public void setTimeoutRate(Double timeoutRate) {
343
+ this.timeoutRate = timeoutRate;
344
+ }
345
+
346
+ public Double getErrorRate() {
347
+ return errorRate;
348
+ }
349
+
350
+ public void setErrorRate(Double errorRate) {
351
+ this.errorRate = errorRate;
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Popular query patterns and their performance.
357
+ */
358
+ public static class PopularQueryDTO {
359
+ private String queryPattern;
360
+ private Integer frequency;
361
+ private String avgExecutionTime;
362
+
363
+ public PopularQueryDTO() {}
364
+
365
+ public PopularQueryDTO(String queryPattern, Integer frequency, String avgExecutionTime) {
366
+ this.queryPattern = queryPattern;
367
+ this.frequency = frequency;
368
+ this.avgExecutionTime = avgExecutionTime;
369
+ }
370
+
371
+ public String getQueryPattern() {
372
+ return queryPattern;
373
+ }
374
+
375
+ public void setQueryPattern(String queryPattern) {
376
+ this.queryPattern = queryPattern;
377
+ }
378
+
379
+ public Integer getFrequency() {
380
+ return frequency;
381
+ }
382
+
383
+ public void setFrequency(Integer frequency) {
384
+ this.frequency = frequency;
385
+ }
386
+
387
+ public String getAvgExecutionTime() {
388
+ return avgExecutionTime;
389
+ }
390
+
391
+ public void setAvgExecutionTime(String avgExecutionTime) {
392
+ this.avgExecutionTime = avgExecutionTime;
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Optimization recommendations based on usage analysis.
398
+ */
399
+ public static class RecommendationDTO {
400
+ private String type; // OPTIMIZATION, MAINTENANCE, SECURITY, COMPLIANCE
401
+ private String priority; // HIGH, MEDIUM, LOW
402
+ private String description;
403
+ private String potentialImpact;
404
+
405
+ public RecommendationDTO() {}
406
+
407
+ public RecommendationDTO(String type, String priority, String description, String potentialImpact) {
408
+ this.type = type;
409
+ this.priority = priority;
410
+ this.description = description;
411
+ this.potentialImpact = potentialImpact;
412
+ }
413
+
414
+ public String getType() {
415
+ return type;
416
+ }
417
+
418
+ public void setType(String type) {
419
+ this.type = type;
420
+ }
421
+
422
+ public String getPriority() {
423
+ return priority;
424
+ }
425
+
426
+ public void setPriority(String priority) {
427
+ this.priority = priority;
428
+ }
429
+
430
+ public String getDescription() {
431
+ return description;
432
+ }
433
+
434
+ public void setDescription(String description) {
435
+ this.description = description;
436
+ }
437
+
438
+ public String getPotentialImpact() {
439
+ return potentialImpact;
440
+ }
441
+
442
+ public void setPotentialImpact(String potentialImpact) {
443
+ this.potentialImpact = potentialImpact;
444
+ }
445
+ }
446
+ }
src/main/java/com/dalab/catalog/dto/BusinessMetadataInputDTO.java ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.List;
4
+ import java.util.UUID;
5
+
6
+ import com.fasterxml.jackson.annotation.JsonInclude;
7
+
8
+ import jakarta.validation.constraints.DecimalMax;
9
+ import jakarta.validation.constraints.DecimalMin;
10
+ import jakarta.validation.constraints.NotBlank;
11
+ import jakarta.validation.constraints.Size;
12
+
13
+ /**
14
+ * Enhanced DTO for business metadata management.
15
+ * Supports steward relationships, compliance classification, and business context.
16
+ */
17
+ @JsonInclude(JsonInclude.Include.NON_NULL)
18
+ public class BusinessMetadataInputDTO {
19
+
20
+ private BusinessMetadataDTO businessMetadata;
21
+ private ComplianceClassificationDTO complianceClassification;
22
+ private String updateReason;
23
+
24
+ public BusinessMetadataInputDTO() {}
25
+
26
+ public BusinessMetadataDTO getBusinessMetadata() {
27
+ return businessMetadata;
28
+ }
29
+
30
+ public void setBusinessMetadata(BusinessMetadataDTO businessMetadata) {
31
+ this.businessMetadata = businessMetadata;
32
+ }
33
+
34
+ public ComplianceClassificationDTO getComplianceClassification() {
35
+ return complianceClassification;
36
+ }
37
+
38
+ public void setComplianceClassification(ComplianceClassificationDTO complianceClassification) {
39
+ this.complianceClassification = complianceClassification;
40
+ }
41
+
42
+ public String getUpdateReason() {
43
+ return updateReason;
44
+ }
45
+
46
+ public void setUpdateReason(String updateReason) {
47
+ this.updateReason = updateReason;
48
+ }
49
+
50
+ /**
51
+ * Core business metadata information
52
+ */
53
+ public static class BusinessMetadataDTO {
54
+
55
+ @Size(max = 255, message = "Business name cannot exceed 255 characters")
56
+ private String businessName;
57
+
58
+ @Size(max = 1000, message = "Business description cannot exceed 1000 characters")
59
+ private String businessDescription;
60
+
61
+ private BusinessContactDTO businessOwner;
62
+ private BusinessContactDTO dataSteward;
63
+
64
+ @NotBlank(message = "Business criticality is required")
65
+ private String businessCriticality; // HIGH, MEDIUM, LOW, CRITICAL
66
+
67
+ @Size(max = 100, message = "Business domain cannot exceed 100 characters")
68
+ private String businessDomain; // CUSTOMER_ANALYTICS, FINANCE, OPERATIONS, etc.
69
+
70
+ private List<String> useCases;
71
+
72
+ @DecimalMin(value = "0.0", message = "Data quality score must be between 0 and 10")
73
+ @DecimalMax(value = "10.0", message = "Data quality score must be between 0 and 10")
74
+ private Double dataQualityScore;
75
+
76
+ private String updateFrequency; // REAL_TIME, HOURLY, DAILY, WEEKLY, MONTHLY, BATCH
77
+
78
+ private RetentionRequirementsDTO retentionRequirements;
79
+
80
+ public BusinessMetadataDTO() {}
81
+
82
+ public String getBusinessName() {
83
+ return businessName;
84
+ }
85
+
86
+ public void setBusinessName(String businessName) {
87
+ this.businessName = businessName;
88
+ }
89
+
90
+ public String getBusinessDescription() {
91
+ return businessDescription;
92
+ }
93
+
94
+ public void setBusinessDescription(String businessDescription) {
95
+ this.businessDescription = businessDescription;
96
+ }
97
+
98
+ public BusinessContactDTO getBusinessOwner() {
99
+ return businessOwner;
100
+ }
101
+
102
+ public void setBusinessOwner(BusinessContactDTO businessOwner) {
103
+ this.businessOwner = businessOwner;
104
+ }
105
+
106
+ public BusinessContactDTO getDataSteward() {
107
+ return dataSteward;
108
+ }
109
+
110
+ public void setDataSteward(BusinessContactDTO dataSteward) {
111
+ this.dataSteward = dataSteward;
112
+ }
113
+
114
+ public String getBusinessCriticality() {
115
+ return businessCriticality;
116
+ }
117
+
118
+ public void setBusinessCriticality(String businessCriticality) {
119
+ this.businessCriticality = businessCriticality;
120
+ }
121
+
122
+ public String getBusinessDomain() {
123
+ return businessDomain;
124
+ }
125
+
126
+ public void setBusinessDomain(String businessDomain) {
127
+ this.businessDomain = businessDomain;
128
+ }
129
+
130
+ public List<String> getUseCases() {
131
+ return useCases;
132
+ }
133
+
134
+ public void setUseCases(List<String> useCases) {
135
+ this.useCases = useCases;
136
+ }
137
+
138
+ public Double getDataQualityScore() {
139
+ return dataQualityScore;
140
+ }
141
+
142
+ public void setDataQualityScore(Double dataQualityScore) {
143
+ this.dataQualityScore = dataQualityScore;
144
+ }
145
+
146
+ public String getUpdateFrequency() {
147
+ return updateFrequency;
148
+ }
149
+
150
+ public void setUpdateFrequency(String updateFrequency) {
151
+ this.updateFrequency = updateFrequency;
152
+ }
153
+
154
+ public RetentionRequirementsDTO getRetentionRequirements() {
155
+ return retentionRequirements;
156
+ }
157
+
158
+ public void setRetentionRequirements(RetentionRequirementsDTO retentionRequirements) {
159
+ this.retentionRequirements = retentionRequirements;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Business contact information for owners and stewards
165
+ */
166
+ public static class BusinessContactDTO {
167
+
168
+ private UUID userId;
169
+
170
+ @NotBlank(message = "Contact name is required")
171
+ @Size(max = 255, message = "Name cannot exceed 255 characters")
172
+ private String name;
173
+
174
+ @NotBlank(message = "Contact email is required")
175
+ @Size(max = 255, message = "Email cannot exceed 255 characters")
176
+ private String email;
177
+
178
+ @Size(max = 100, message = "Department cannot exceed 100 characters")
179
+ private String department;
180
+
181
+ @Size(max = 100, message = "Role cannot exceed 100 characters")
182
+ private String role;
183
+
184
+ @Size(max = 50, message = "Phone cannot exceed 50 characters")
185
+ private String phone;
186
+
187
+ public BusinessContactDTO() {}
188
+
189
+ public BusinessContactDTO(UUID userId, String name, String email, String department) {
190
+ this.userId = userId;
191
+ this.name = name;
192
+ this.email = email;
193
+ this.department = department;
194
+ }
195
+
196
+ public UUID getUserId() {
197
+ return userId;
198
+ }
199
+
200
+ public void setUserId(UUID userId) {
201
+ this.userId = userId;
202
+ }
203
+
204
+ public String getName() {
205
+ return name;
206
+ }
207
+
208
+ public void setName(String name) {
209
+ this.name = name;
210
+ }
211
+
212
+ public String getEmail() {
213
+ return email;
214
+ }
215
+
216
+ public void setEmail(String email) {
217
+ this.email = email;
218
+ }
219
+
220
+ public String getDepartment() {
221
+ return department;
222
+ }
223
+
224
+ public void setDepartment(String department) {
225
+ this.department = department;
226
+ }
227
+
228
+ public String getRole() {
229
+ return role;
230
+ }
231
+
232
+ public void setRole(String role) {
233
+ this.role = role;
234
+ }
235
+
236
+ public String getPhone() {
237
+ return phone;
238
+ }
239
+
240
+ public void setPhone(String phone) {
241
+ this.phone = phone;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Data retention requirements and policies
247
+ */
248
+ public static class RetentionRequirementsDTO {
249
+
250
+ private String legalRetention; // 7_YEARS, 10_YEARS, INDEFINITE, etc.
251
+ private String businessRetention; // 5_YEARS, 3_YEARS, etc.
252
+ private String archiveAfter; // 2_YEARS, 1_YEAR, etc.
253
+ private String deleteAfter; // 10_YEARS, NEVER, etc.
254
+ private List<String> retentionReasons; // LEGAL_REQUIREMENT, BUSINESS_NEED, REGULATORY_COMPLIANCE
255
+
256
+ public RetentionRequirementsDTO() {}
257
+
258
+ public String getLegalRetention() {
259
+ return legalRetention;
260
+ }
261
+
262
+ public void setLegalRetention(String legalRetention) {
263
+ this.legalRetention = legalRetention;
264
+ }
265
+
266
+ public String getBusinessRetention() {
267
+ return businessRetention;
268
+ }
269
+
270
+ public void setBusinessRetention(String businessRetention) {
271
+ this.businessRetention = businessRetention;
272
+ }
273
+
274
+ public String getArchiveAfter() {
275
+ return archiveAfter;
276
+ }
277
+
278
+ public void setArchiveAfter(String archiveAfter) {
279
+ this.archiveAfter = archiveAfter;
280
+ }
281
+
282
+ public String getDeleteAfter() {
283
+ return deleteAfter;
284
+ }
285
+
286
+ public void setDeleteAfter(String deleteAfter) {
287
+ this.deleteAfter = deleteAfter;
288
+ }
289
+
290
+ public List<String> getRetentionReasons() {
291
+ return retentionReasons;
292
+ }
293
+
294
+ public void setRetentionReasons(List<String> retentionReasons) {
295
+ this.retentionReasons = retentionReasons;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Compliance and data classification information
301
+ */
302
+ public static class ComplianceClassificationDTO {
303
+
304
+ @NotBlank(message = "Data classification is required")
305
+ private String dataClassification; // PUBLIC, INTERNAL, CONFIDENTIAL, RESTRICTED
306
+
307
+ private Boolean containsPii;
308
+ private Boolean containsPhi; // Protected Health Information
309
+ private Boolean containsFinancial;
310
+ private Boolean gdprApplicable;
311
+ private Boolean soxRelevant; // Sarbanes-Oxley
312
+ private Boolean hipaaRelevant;
313
+ private Boolean pciRelevant; // Payment Card Industry
314
+
315
+ private List<String> regulatoryFrameworks; // GDPR, CCPA, SOX, HIPAA, PCI_DSS
316
+ private List<String> dataSubjectCategories; // CUSTOMER, EMPLOYEE, PARTNER, VENDOR
317
+ private String privacyImpactLevel; // HIGH, MEDIUM, LOW, NONE
318
+
319
+ public ComplianceClassificationDTO() {}
320
+
321
+ public String getDataClassification() {
322
+ return dataClassification;
323
+ }
324
+
325
+ public void setDataClassification(String dataClassification) {
326
+ this.dataClassification = dataClassification;
327
+ }
328
+
329
+ public Boolean getContainsPii() {
330
+ return containsPii;
331
+ }
332
+
333
+ public void setContainsPii(Boolean containsPii) {
334
+ this.containsPii = containsPii;
335
+ }
336
+
337
+ public Boolean getContainsPhi() {
338
+ return containsPhi;
339
+ }
340
+
341
+ public void setContainsPhi(Boolean containsPhi) {
342
+ this.containsPhi = containsPhi;
343
+ }
344
+
345
+ public Boolean getContainsFinancial() {
346
+ return containsFinancial;
347
+ }
348
+
349
+ public void setContainsFinancial(Boolean containsFinancial) {
350
+ this.containsFinancial = containsFinancial;
351
+ }
352
+
353
+ public Boolean getGdprApplicable() {
354
+ return gdprApplicable;
355
+ }
356
+
357
+ public void setGdprApplicable(Boolean gdprApplicable) {
358
+ this.gdprApplicable = gdprApplicable;
359
+ }
360
+
361
+ public Boolean getSoxRelevant() {
362
+ return soxRelevant;
363
+ }
364
+
365
+ public void setSoxRelevant(Boolean soxRelevant) {
366
+ this.soxRelevant = soxRelevant;
367
+ }
368
+
369
+ public Boolean getHipaaRelevant() {
370
+ return hipaaRelevant;
371
+ }
372
+
373
+ public void setHipaaRelevant(Boolean hipaaRelevant) {
374
+ this.hipaaRelevant = hipaaRelevant;
375
+ }
376
+
377
+ public Boolean getPciRelevant() {
378
+ return pciRelevant;
379
+ }
380
+
381
+ public void setPciRelevant(Boolean pciRelevant) {
382
+ this.pciRelevant = pciRelevant;
383
+ }
384
+
385
+ public List<String> getRegulatoryFrameworks() {
386
+ return regulatoryFrameworks;
387
+ }
388
+
389
+ public void setRegulatoryFrameworks(List<String> regulatoryFrameworks) {
390
+ this.regulatoryFrameworks = regulatoryFrameworks;
391
+ }
392
+
393
+ public List<String> getDataSubjectCategories() {
394
+ return dataSubjectCategories;
395
+ }
396
+
397
+ public void setDataSubjectCategories(List<String> dataSubjectCategories) {
398
+ this.dataSubjectCategories = dataSubjectCategories;
399
+ }
400
+
401
+ public String getPrivacyImpactLevel() {
402
+ return privacyImpactLevel;
403
+ }
404
+
405
+ public void setPrivacyImpactLevel(String privacyImpactLevel) {
406
+ this.privacyImpactLevel = privacyImpactLevel;
407
+ }
408
+ }
409
+ }
src/main/java/com/dalab/catalog/dto/BusinessMetadataResponseDTO.java ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.UUID;
6
+
7
+ import com.fasterxml.jackson.annotation.JsonInclude;
8
+
9
+ /**
10
+ * Enhanced response DTO for business metadata.
11
+ * Includes audit information, validation status, and complete business context.
12
+ */
13
+ @JsonInclude(JsonInclude.Include.NON_NULL)
14
+ public class BusinessMetadataResponseDTO {
15
+
16
+ private BusinessMetadataDTO businessMetadata;
17
+ private ComplianceClassificationDTO complianceClassification;
18
+ private MetadataAuditDTO auditInfo;
19
+ private ValidationStatusDTO validationStatus;
20
+ private List<RecommendationDTO> recommendations;
21
+
22
+ public BusinessMetadataResponseDTO() {}
23
+
24
+ public BusinessMetadataDTO getBusinessMetadata() {
25
+ return businessMetadata;
26
+ }
27
+
28
+ public void setBusinessMetadata(BusinessMetadataDTO businessMetadata) {
29
+ this.businessMetadata = businessMetadata;
30
+ }
31
+
32
+ public ComplianceClassificationDTO getComplianceClassification() {
33
+ return complianceClassification;
34
+ }
35
+
36
+ public void setComplianceClassification(ComplianceClassificationDTO complianceClassification) {
37
+ this.complianceClassification = complianceClassification;
38
+ }
39
+
40
+ public MetadataAuditDTO getAuditInfo() {
41
+ return auditInfo;
42
+ }
43
+
44
+ public void setAuditInfo(MetadataAuditDTO auditInfo) {
45
+ this.auditInfo = auditInfo;
46
+ }
47
+
48
+ public ValidationStatusDTO getValidationStatus() {
49
+ return validationStatus;
50
+ }
51
+
52
+ public void setValidationStatus(ValidationStatusDTO validationStatus) {
53
+ this.validationStatus = validationStatus;
54
+ }
55
+
56
+ public List<RecommendationDTO> getRecommendations() {
57
+ return recommendations;
58
+ }
59
+
60
+ public void setRecommendations(List<RecommendationDTO> recommendations) {
61
+ this.recommendations = recommendations;
62
+ }
63
+
64
+ /**
65
+ * Reusing BusinessMetadataDTO from input for consistency
66
+ */
67
+ public static class BusinessMetadataDTO extends BusinessMetadataInputDTO.BusinessMetadataDTO {
68
+ // Inherits all fields from input DTO for consistency
69
+ }
70
+
71
+ /**
72
+ * Reusing ComplianceClassificationDTO from input for consistency
73
+ */
74
+ public static class ComplianceClassificationDTO extends BusinessMetadataInputDTO.ComplianceClassificationDTO {
75
+ // Inherits all fields from input DTO for consistency
76
+ }
77
+
78
+ /**
79
+ * Audit information for metadata changes
80
+ */
81
+ public static class MetadataAuditDTO {
82
+
83
+ private UUID lastUpdatedBy;
84
+ private String lastUpdatedByName;
85
+ private Instant lastUpdatedAt;
86
+ private String updateReason;
87
+ private Integer versionNumber;
88
+ private UUID createdBy;
89
+ private String createdByName;
90
+ private Instant createdAt;
91
+ private List<String> changeHistory; // Summary of recent changes
92
+
93
+ public MetadataAuditDTO() {}
94
+
95
+ public UUID getLastUpdatedBy() {
96
+ return lastUpdatedBy;
97
+ }
98
+
99
+ public void setLastUpdatedBy(UUID lastUpdatedBy) {
100
+ this.lastUpdatedBy = lastUpdatedBy;
101
+ }
102
+
103
+ public String getLastUpdatedByName() {
104
+ return lastUpdatedByName;
105
+ }
106
+
107
+ public void setLastUpdatedByName(String lastUpdatedByName) {
108
+ this.lastUpdatedByName = lastUpdatedByName;
109
+ }
110
+
111
+ public Instant getLastUpdatedAt() {
112
+ return lastUpdatedAt;
113
+ }
114
+
115
+ public void setLastUpdatedAt(Instant lastUpdatedAt) {
116
+ this.lastUpdatedAt = lastUpdatedAt;
117
+ }
118
+
119
+ public String getUpdateReason() {
120
+ return updateReason;
121
+ }
122
+
123
+ public void setUpdateReason(String updateReason) {
124
+ this.updateReason = updateReason;
125
+ }
126
+
127
+ public Integer getVersionNumber() {
128
+ return versionNumber;
129
+ }
130
+
131
+ public void setVersionNumber(Integer versionNumber) {
132
+ this.versionNumber = versionNumber;
133
+ }
134
+
135
+ public UUID getCreatedBy() {
136
+ return createdBy;
137
+ }
138
+
139
+ public void setCreatedBy(UUID createdBy) {
140
+ this.createdBy = createdBy;
141
+ }
142
+
143
+ public String getCreatedByName() {
144
+ return createdByName;
145
+ }
146
+
147
+ public void setCreatedByName(String createdByName) {
148
+ this.createdByName = createdByName;
149
+ }
150
+
151
+ public Instant getCreatedAt() {
152
+ return createdAt;
153
+ }
154
+
155
+ public void setCreatedAt(Instant createdAt) {
156
+ this.createdAt = createdAt;
157
+ }
158
+
159
+ public List<String> getChangeHistory() {
160
+ return changeHistory;
161
+ }
162
+
163
+ public void setChangeHistory(List<String> changeHistory) {
164
+ this.changeHistory = changeHistory;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Validation status for business metadata completeness
170
+ */
171
+ public static class ValidationStatusDTO {
172
+
173
+ private Boolean isComplete;
174
+ private Double completenessScore; // 0.0 to 1.0
175
+ private List<String> missingFields;
176
+ private List<String> validationWarnings;
177
+ private List<String> validationErrors;
178
+ private String overallStatus; // COMPLETE, PARTIAL, INCOMPLETE, INVALID
179
+
180
+ public ValidationStatusDTO() {}
181
+
182
+ public Boolean getIsComplete() {
183
+ return isComplete;
184
+ }
185
+
186
+ public void setIsComplete(Boolean isComplete) {
187
+ this.isComplete = isComplete;
188
+ }
189
+
190
+ public Double getCompletenessScore() {
191
+ return completenessScore;
192
+ }
193
+
194
+ public void setCompletenessScore(Double completenessScore) {
195
+ this.completenessScore = completenessScore;
196
+ }
197
+
198
+ public List<String> getMissingFields() {
199
+ return missingFields;
200
+ }
201
+
202
+ public void setMissingFields(List<String> missingFields) {
203
+ this.missingFields = missingFields;
204
+ }
205
+
206
+ public List<String> getValidationWarnings() {
207
+ return validationWarnings;
208
+ }
209
+
210
+ public void setValidationWarnings(List<String> validationWarnings) {
211
+ this.validationWarnings = validationWarnings;
212
+ }
213
+
214
+ public List<String> getValidationErrors() {
215
+ return validationErrors;
216
+ }
217
+
218
+ public void setValidationErrors(List<String> validationErrors) {
219
+ this.validationErrors = validationErrors;
220
+ }
221
+
222
+ public String getOverallStatus() {
223
+ return overallStatus;
224
+ }
225
+
226
+ public void setOverallStatus(String overallStatus) {
227
+ this.overallStatus = overallStatus;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Recommendations for improving metadata quality
233
+ */
234
+ public static class RecommendationDTO {
235
+
236
+ private String type; // COMPLETENESS, COMPLIANCE, GOVERNANCE, QUALITY
237
+ private String priority; // HIGH, MEDIUM, LOW
238
+ private String title;
239
+ private String description;
240
+ private String actionRequired;
241
+ private String potentialImpact;
242
+
243
+ public RecommendationDTO() {}
244
+
245
+ public RecommendationDTO(String type, String priority, String title, String description, String actionRequired, String potentialImpact) {
246
+ this.type = type;
247
+ this.priority = priority;
248
+ this.title = title;
249
+ this.description = description;
250
+ this.actionRequired = actionRequired;
251
+ this.potentialImpact = potentialImpact;
252
+ }
253
+
254
+ public String getType() {
255
+ return type;
256
+ }
257
+
258
+ public void setType(String type) {
259
+ this.type = type;
260
+ }
261
+
262
+ public String getPriority() {
263
+ return priority;
264
+ }
265
+
266
+ public void setPriority(String priority) {
267
+ this.priority = priority;
268
+ }
269
+
270
+ public String getTitle() {
271
+ return title;
272
+ }
273
+
274
+ public void setTitle(String title) {
275
+ this.title = title;
276
+ }
277
+
278
+ public String getDescription() {
279
+ return description;
280
+ }
281
+
282
+ public void setDescription(String description) {
283
+ this.description = description;
284
+ }
285
+
286
+ public String getActionRequired() {
287
+ return actionRequired;
288
+ }
289
+
290
+ public void setActionRequired(String actionRequired) {
291
+ this.actionRequired = actionRequired;
292
+ }
293
+
294
+ public String getPotentialImpact() {
295
+ return potentialImpact;
296
+ }
297
+
298
+ public void setPotentialImpact(String potentialImpact) {
299
+ this.potentialImpact = potentialImpact;
300
+ }
301
+ }
302
+ }
src/main/java/com/dalab/catalog/dto/CatalogFiltersDTO.java ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+
7
+ import com.fasterxml.jackson.annotation.JsonInclude;
8
+
9
+ /**
10
+ * DTO for Catalog Faceted Search Filters.
11
+ * Priority 2 endpoint: Provides filter metadata for advanced search and filtering in the catalog UI.
12
+ */
13
+ @JsonInclude(JsonInclude.Include.NON_NULL)
14
+ public class CatalogFiltersDTO {
15
+
16
+ private List<FilterCategoryDTO> filterCategories;
17
+ private Map<String, List<String>> quickFilters;
18
+ private SearchOptionsDTO searchOptions;
19
+ private Instant lastUpdated;
20
+ private Integer totalAssets;
21
+ private String catalogVersion;
22
+
23
+ /**
24
+ * Default constructor.
25
+ */
26
+ public CatalogFiltersDTO() {}
27
+
28
+ /**
29
+ * DTO for filter category information.
30
+ */
31
+ @JsonInclude(JsonInclude.Include.NON_NULL)
32
+ public static class FilterCategoryDTO {
33
+
34
+ private String categoryName;
35
+ private String displayName;
36
+ private String categoryType; // SINGLE_SELECT, MULTI_SELECT, RANGE, DATE_RANGE, TEXT_SEARCH
37
+ private List<FilterOptionDTO> options;
38
+ private Integer totalOptions;
39
+ private String description;
40
+ private Boolean isExpandedByDefault;
41
+ private Integer displayOrder;
42
+ private Map<String, Object> metadata;
43
+
44
+ public FilterCategoryDTO() {}
45
+
46
+ public FilterCategoryDTO(String categoryName, String displayName, String categoryType) {
47
+ this.categoryName = categoryName;
48
+ this.displayName = displayName;
49
+ this.categoryType = categoryType;
50
+ }
51
+
52
+ // Getters and setters
53
+ public String getCategoryName() { return categoryName; }
54
+ public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
55
+ public String getDisplayName() { return displayName; }
56
+ public void setDisplayName(String displayName) { this.displayName = displayName; }
57
+ public String getCategoryType() { return categoryType; }
58
+ public void setCategoryType(String categoryType) { this.categoryType = categoryType; }
59
+ public List<FilterOptionDTO> getOptions() { return options; }
60
+ public void setOptions(List<FilterOptionDTO> options) { this.options = options; }
61
+ public Integer getTotalOptions() { return totalOptions; }
62
+ public void setTotalOptions(Integer totalOptions) { this.totalOptions = totalOptions; }
63
+ public String getDescription() { return description; }
64
+ public void setDescription(String description) { this.description = description; }
65
+ public Boolean getIsExpandedByDefault() { return isExpandedByDefault; }
66
+ public void setIsExpandedByDefault(Boolean isExpandedByDefault) { this.isExpandedByDefault = isExpandedByDefault; }
67
+ public Integer getDisplayOrder() { return displayOrder; }
68
+ public void setDisplayOrder(Integer displayOrder) { this.displayOrder = displayOrder; }
69
+ public Map<String, Object> getMetadata() { return metadata; }
70
+ public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
71
+ }
72
+
73
+ /**
74
+ * DTO for individual filter option.
75
+ */
76
+ @JsonInclude(JsonInclude.Include.NON_NULL)
77
+ public static class FilterOptionDTO {
78
+
79
+ private String value;
80
+ private String displayValue;
81
+ private Integer count;
82
+ private String description;
83
+ private Boolean isSelected;
84
+ private String icon;
85
+ private String color;
86
+ private List<FilterOptionDTO> subOptions;
87
+ private Map<String, Object> metadata;
88
+
89
+ public FilterOptionDTO() {}
90
+
91
+ public FilterOptionDTO(String value, String displayValue, Integer count) {
92
+ this.value = value;
93
+ this.displayValue = displayValue;
94
+ this.count = count;
95
+ }
96
+
97
+ // Getters and setters
98
+ public String getValue() { return value; }
99
+ public void setValue(String value) { this.value = value; }
100
+ public String getDisplayValue() { return displayValue; }
101
+ public void setDisplayValue(String displayValue) { this.displayValue = displayValue; }
102
+ public Integer getCount() { return count; }
103
+ public void setCount(Integer count) { this.count = count; }
104
+ public String getDescription() { return description; }
105
+ public void setDescription(String description) { this.description = description; }
106
+ public Boolean getIsSelected() { return isSelected; }
107
+ public void setIsSelected(Boolean isSelected) { this.isSelected = isSelected; }
108
+ public String getIcon() { return icon; }
109
+ public void setIcon(String icon) { this.icon = icon; }
110
+ public String getColor() { return color; }
111
+ public void setColor(String color) { this.color = color; }
112
+ public List<FilterOptionDTO> getSubOptions() { return subOptions; }
113
+ public void setSubOptions(List<FilterOptionDTO> subOptions) { this.subOptions = subOptions; }
114
+ public Map<String, Object> getMetadata() { return metadata; }
115
+ public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
116
+ }
117
+
118
+ /**
119
+ * DTO for search options and capabilities.
120
+ */
121
+ @JsonInclude(JsonInclude.Include.NON_NULL)
122
+ public static class SearchOptionsDTO {
123
+
124
+ private List<String> searchableFields;
125
+ private List<String> sortableFields;
126
+ private List<SortOptionDTO> defaultSortOptions;
127
+ private Integer maxResults;
128
+ private Integer defaultPageSize;
129
+ private List<String> supportedOperators;
130
+ private Boolean supportsFullTextSearch;
131
+ private Boolean supportsWildcards;
132
+ private Boolean supportsFuzzySearch;
133
+ private Map<String, Object> advancedOptions;
134
+
135
+ public SearchOptionsDTO() {}
136
+
137
+ // Getters and setters
138
+ public List<String> getSearchableFields() { return searchableFields; }
139
+ public void setSearchableFields(List<String> searchableFields) { this.searchableFields = searchableFields; }
140
+ public List<String> getSortableFields() { return sortableFields; }
141
+ public void setSortableFields(List<String> sortableFields) { this.sortableFields = sortableFields; }
142
+ public List<SortOptionDTO> getDefaultSortOptions() { return defaultSortOptions; }
143
+ public void setDefaultSortOptions(List<SortOptionDTO> defaultSortOptions) { this.defaultSortOptions = defaultSortOptions; }
144
+ public Integer getMaxResults() { return maxResults; }
145
+ public void setMaxResults(Integer maxResults) { this.maxResults = maxResults; }
146
+ public Integer getDefaultPageSize() { return defaultPageSize; }
147
+ public void setDefaultPageSize(Integer defaultPageSize) { this.defaultPageSize = defaultPageSize; }
148
+ public List<String> getSupportedOperators() { return supportedOperators; }
149
+ public void setSupportedOperators(List<String> supportedOperators) { this.supportedOperators = supportedOperators; }
150
+ public Boolean getSupportsFullTextSearch() { return supportsFullTextSearch; }
151
+ public void setSupportsFullTextSearch(Boolean supportsFullTextSearch) { this.supportsFullTextSearch = supportsFullTextSearch; }
152
+ public Boolean getSupportsWildcards() { return supportsWildcards; }
153
+ public void setSupportsWildcards(Boolean supportsWildcards) { this.supportsWildcards = supportsWildcards; }
154
+ public Boolean getSupportsFuzzySearch() { return supportsFuzzySearch; }
155
+ public void setSupportsFuzzySearch(Boolean supportsFuzzySearch) { this.supportsFuzzySearch = supportsFuzzySearch; }
156
+ public Map<String, Object> getAdvancedOptions() { return advancedOptions; }
157
+ public void setAdvancedOptions(Map<String, Object> advancedOptions) { this.advancedOptions = advancedOptions; }
158
+ }
159
+
160
+ /**
161
+ * DTO for sort option.
162
+ */
163
+ @JsonInclude(JsonInclude.Include.NON_NULL)
164
+ public static class SortOptionDTO {
165
+
166
+ private String field;
167
+ private String direction; // ASC, DESC
168
+ private String displayName;
169
+ private Boolean isDefault;
170
+
171
+ public SortOptionDTO() {}
172
+
173
+ public SortOptionDTO(String field, String direction, String displayName) {
174
+ this.field = field;
175
+ this.direction = direction;
176
+ this.displayName = displayName;
177
+ }
178
+
179
+ // Getters and setters
180
+ public String getField() { return field; }
181
+ public void setField(String field) { this.field = field; }
182
+ public String getDirection() { return direction; }
183
+ public void setDirection(String direction) { this.direction = direction; }
184
+ public String getDisplayName() { return displayName; }
185
+ public void setDisplayName(String displayName) { this.displayName = displayName; }
186
+ public Boolean getIsDefault() { return isDefault; }
187
+ public void setIsDefault(Boolean isDefault) { this.isDefault = isDefault; }
188
+ }
189
+
190
+ // Main class getters and setters
191
+ public List<FilterCategoryDTO> getFilterCategories() { return filterCategories; }
192
+ public void setFilterCategories(List<FilterCategoryDTO> filterCategories) { this.filterCategories = filterCategories; }
193
+ public Map<String, List<String>> getQuickFilters() { return quickFilters; }
194
+ public void setQuickFilters(Map<String, List<String>> quickFilters) { this.quickFilters = quickFilters; }
195
+ public SearchOptionsDTO getSearchOptions() { return searchOptions; }
196
+ public void setSearchOptions(SearchOptionsDTO searchOptions) { this.searchOptions = searchOptions; }
197
+ public Instant getLastUpdated() { return lastUpdated; }
198
+ public void setLastUpdated(Instant lastUpdated) { this.lastUpdated = lastUpdated; }
199
+ public Integer getTotalAssets() { return totalAssets; }
200
+ public void setTotalAssets(Integer totalAssets) { this.totalAssets = totalAssets; }
201
+ public String getCatalogVersion() { return catalogVersion; }
202
+ public void setCatalogVersion(String catalogVersion) { this.catalogVersion = catalogVersion; }
203
+ }
src/main/java/com/dalab/catalog/dto/EnhancedLineageDTO.java ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+ import java.util.UUID;
7
+
8
+ import com.fasterxml.jackson.annotation.JsonInclude;
9
+
10
+ /**
11
+ * DTO for Enhanced Data Lineage Visualization.
12
+ * Priority 2 endpoint: Provides detailed lineage information with visualization metadata for graph rendering.
13
+ */
14
+ @JsonInclude(JsonInclude.Include.NON_NULL)
15
+ public class EnhancedLineageDTO {
16
+
17
+ private UUID assetId;
18
+ private String assetName;
19
+ private LineageGraphDTO lineageGraph;
20
+ private LineageStatisticsDTO statistics;
21
+ private List<LineagePathDTO> criticalPaths;
22
+ private List<LineageIssueDTO> issues;
23
+ private LineageVisualizationMetadataDTO visualizationMetadata;
24
+ private Instant lastUpdated;
25
+ private String lineageVersion;
26
+ private Integer maxDepth;
27
+
28
+ /**
29
+ * Default constructor.
30
+ */
31
+ public EnhancedLineageDTO() {}
32
+
33
+ /**
34
+ * Constructor with basic information.
35
+ */
36
+ public EnhancedLineageDTO(UUID assetId, String assetName) {
37
+ this.assetId = assetId;
38
+ this.assetName = assetName;
39
+ }
40
+
41
+ /**
42
+ * DTO for lineage graph structure.
43
+ */
44
+ @JsonInclude(JsonInclude.Include.NON_NULL)
45
+ public static class LineageGraphDTO {
46
+
47
+ private List<LineageNodeDTO> nodes;
48
+ private List<LineageEdgeDTO> edges;
49
+ private Map<String, Object> layout; // Positioning and layout information for UI
50
+ private Integer totalNodes;
51
+ private Integer totalEdges;
52
+ private Integer maxDepth;
53
+ private List<String> layers; // Hierarchical layer information
54
+
55
+ public LineageGraphDTO() {}
56
+
57
+ // Getters and setters
58
+ public List<LineageNodeDTO> getNodes() { return nodes; }
59
+ public void setNodes(List<LineageNodeDTO> nodes) { this.nodes = nodes; }
60
+ public List<LineageEdgeDTO> getEdges() { return edges; }
61
+ public void setEdges(List<LineageEdgeDTO> edges) { this.edges = edges; }
62
+ public Map<String, Object> getLayout() { return layout; }
63
+ public void setLayout(Map<String, Object> layout) { this.layout = layout; }
64
+ public Integer getTotalNodes() { return totalNodes; }
65
+ public void setTotalNodes(Integer totalNodes) { this.totalNodes = totalNodes; }
66
+ public Integer getTotalEdges() { return totalEdges; }
67
+ public void setTotalEdges(Integer totalEdges) { this.totalEdges = totalEdges; }
68
+ public Integer getMaxDepth() { return maxDepth; }
69
+ public void setMaxDepth(Integer maxDepth) { this.maxDepth = maxDepth; }
70
+ public List<String> getLayers() { return layers; }
71
+ public void setLayers(List<String> layers) { this.layers = layers; }
72
+ }
73
+
74
+ /**
75
+ * DTO for lineage node information.
76
+ */
77
+ @JsonInclude(JsonInclude.Include.NON_NULL)
78
+ public static class LineageNodeDTO {
79
+
80
+ private UUID nodeId;
81
+ private String nodeName;
82
+ private String nodeType; // ASSET, TRANSFORMATION, PROCESS, EXTERNAL_SOURCE
83
+ private String assetType; // TABLE, VIEW, FILE, API, etc.
84
+ private String description;
85
+ private Map<String, Object> position; // x, y coordinates for visualization
86
+ private String status; // ACTIVE, INACTIVE, DEPRECATED, UNKNOWN
87
+ private List<String> tags;
88
+ private Map<String, Object> metadata;
89
+ private NodeStatsDTO statistics;
90
+ private String icon; // Icon identifier for UI
91
+ private String color; // Color code for visualization
92
+ private Integer layer; // Hierarchical layer number
93
+
94
+ public LineageNodeDTO() {}
95
+
96
+ public LineageNodeDTO(UUID nodeId, String nodeName, String nodeType) {
97
+ this.nodeId = nodeId;
98
+ this.nodeName = nodeName;
99
+ this.nodeType = nodeType;
100
+ }
101
+
102
+ // Getters and setters
103
+ public UUID getNodeId() { return nodeId; }
104
+ public void setNodeId(UUID nodeId) { this.nodeId = nodeId; }
105
+ public String getNodeName() { return nodeName; }
106
+ public void setNodeName(String nodeName) { this.nodeName = nodeName; }
107
+ public String getNodeType() { return nodeType; }
108
+ public void setNodeType(String nodeType) { this.nodeType = nodeType; }
109
+ public String getAssetType() { return assetType; }
110
+ public void setAssetType(String assetType) { this.assetType = assetType; }
111
+ public String getDescription() { return description; }
112
+ public void setDescription(String description) { this.description = description; }
113
+ public Map<String, Object> getPosition() { return position; }
114
+ public void setPosition(Map<String, Object> position) { this.position = position; }
115
+ public String getStatus() { return status; }
116
+ public void setStatus(String status) { this.status = status; }
117
+ public List<String> getTags() { return tags; }
118
+ public void setTags(List<String> tags) { this.tags = tags; }
119
+ public Map<String, Object> getMetadata() { return metadata; }
120
+ public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
121
+ public NodeStatsDTO getStatistics() { return statistics; }
122
+ public void setStatistics(NodeStatsDTO statistics) { this.statistics = statistics; }
123
+ public String getIcon() { return icon; }
124
+ public void setIcon(String icon) { this.icon = icon; }
125
+ public String getColor() { return color; }
126
+ public void setColor(String color) { this.color = color; }
127
+ public Integer getLayer() { return layer; }
128
+ public void setLayer(Integer layer) { this.layer = layer; }
129
+ }
130
+
131
+ /**
132
+ * DTO for node statistics.
133
+ */
134
+ @JsonInclude(JsonInclude.Include.NON_NULL)
135
+ public static class NodeStatsDTO {
136
+
137
+ private Long recordCount;
138
+ private Long sizeBytes;
139
+ private Instant lastAccessed;
140
+ private Instant lastModified;
141
+ private Integer usageFrequency;
142
+ private String healthStatus; // HEALTHY, WARNING, ERROR, UNKNOWN
143
+
144
+ public NodeStatsDTO() {}
145
+
146
+ // Getters and setters
147
+ public Long getRecordCount() { return recordCount; }
148
+ public void setRecordCount(Long recordCount) { this.recordCount = recordCount; }
149
+ public Long getSizeBytes() { return sizeBytes; }
150
+ public void setSizeBytes(Long sizeBytes) { this.sizeBytes = sizeBytes; }
151
+ public Instant getLastAccessed() { return lastAccessed; }
152
+ public void setLastAccessed(Instant lastAccessed) { this.lastAccessed = lastAccessed; }
153
+ public Instant getLastModified() { return lastModified; }
154
+ public void setLastModified(Instant lastModified) { this.lastModified = lastModified; }
155
+ public Integer getUsageFrequency() { return usageFrequency; }
156
+ public void setUsageFrequency(Integer usageFrequency) { this.usageFrequency = usageFrequency; }
157
+ public String getHealthStatus() { return healthStatus; }
158
+ public void setHealthStatus(String healthStatus) { this.healthStatus = healthStatus; }
159
+ }
160
+
161
+ /**
162
+ * DTO for lineage edge (relationship) information.
163
+ */
164
+ @JsonInclude(JsonInclude.Include.NON_NULL)
165
+ public static class LineageEdgeDTO {
166
+
167
+ private String edgeId;
168
+ private UUID sourceNodeId;
169
+ private UUID targetNodeId;
170
+ private String relationshipType; // DERIVED_FROM, WRITES_TO, READS_FROM, TRANSFORMS_TO
171
+ private String transformationType; // ETL, ELT, COPY, AGGREGATE, JOIN, FILTER, etc.
172
+ private List<FieldMappingDTO> fieldMappings;
173
+ private String description;
174
+ private Map<String, Object> transformationLogic;
175
+ private Instant createdAt;
176
+ private Instant lastExecuted;
177
+ private String executionStatus; // SUCCESS, FAILED, RUNNING, SCHEDULED
178
+ private EdgeStatsDTO statistics;
179
+ private String style; // Styling information for visualization (dashed, solid, etc.)
180
+ private String color; // Color for the edge in visualization
181
+
182
+ public LineageEdgeDTO() {}
183
+
184
+ public LineageEdgeDTO(String edgeId, UUID sourceNodeId, UUID targetNodeId, String relationshipType) {
185
+ this.edgeId = edgeId;
186
+ this.sourceNodeId = sourceNodeId;
187
+ this.targetNodeId = targetNodeId;
188
+ this.relationshipType = relationshipType;
189
+ }
190
+
191
+ // Getters and setters
192
+ public String getEdgeId() { return edgeId; }
193
+ public void setEdgeId(String edgeId) { this.edgeId = edgeId; }
194
+ public UUID getSourceNodeId() { return sourceNodeId; }
195
+ public void setSourceNodeId(UUID sourceNodeId) { this.sourceNodeId = sourceNodeId; }
196
+ public UUID getTargetNodeId() { return targetNodeId; }
197
+ public void setTargetNodeId(UUID targetNodeId) { this.targetNodeId = targetNodeId; }
198
+ public String getRelationshipType() { return relationshipType; }
199
+ public void setRelationshipType(String relationshipType) { this.relationshipType = relationshipType; }
200
+ public String getTransformationType() { return transformationType; }
201
+ public void setTransformationType(String transformationType) { this.transformationType = transformationType; }
202
+ public List<FieldMappingDTO> getFieldMappings() { return fieldMappings; }
203
+ public void setFieldMappings(List<FieldMappingDTO> fieldMappings) { this.fieldMappings = fieldMappings; }
204
+ public String getDescription() { return description; }
205
+ public void setDescription(String description) { this.description = description; }
206
+ public Map<String, Object> getTransformationLogic() { return transformationLogic; }
207
+ public void setTransformationLogic(Map<String, Object> transformationLogic) { this.transformationLogic = transformationLogic; }
208
+ public Instant getCreatedAt() { return createdAt; }
209
+ public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
210
+ public Instant getLastExecuted() { return lastExecuted; }
211
+ public void setLastExecuted(Instant lastExecuted) { this.lastExecuted = lastExecuted; }
212
+ public String getExecutionStatus() { return executionStatus; }
213
+ public void setExecutionStatus(String executionStatus) { this.executionStatus = executionStatus; }
214
+ public EdgeStatsDTO getStatistics() { return statistics; }
215
+ public void setStatistics(EdgeStatsDTO statistics) { this.statistics = statistics; }
216
+ public String getStyle() { return style; }
217
+ public void setStyle(String style) { this.style = style; }
218
+ public String getColor() { return color; }
219
+ public void setColor(String color) { this.color = color; }
220
+ }
221
+
222
+ /**
223
+ * DTO for field mapping information.
224
+ */
225
+ @JsonInclude(JsonInclude.Include.NON_NULL)
226
+ public static class FieldMappingDTO {
227
+
228
+ private String sourceField;
229
+ private String targetField;
230
+ private String mappingType; // DIRECT, TRANSFORMED, CALCULATED, DERIVED
231
+ private String transformation;
232
+ private Double confidence; // 0.0 to 1.0
233
+
234
+ public FieldMappingDTO() {}
235
+
236
+ public FieldMappingDTO(String sourceField, String targetField, String mappingType) {
237
+ this.sourceField = sourceField;
238
+ this.targetField = targetField;
239
+ this.mappingType = mappingType;
240
+ }
241
+
242
+ // Getters and setters
243
+ public String getSourceField() { return sourceField; }
244
+ public void setSourceField(String sourceField) { this.sourceField = sourceField; }
245
+ public String getTargetField() { return targetField; }
246
+ public void setTargetField(String targetField) { this.targetField = targetField; }
247
+ public String getMappingType() { return mappingType; }
248
+ public void setMappingType(String mappingType) { this.mappingType = mappingType; }
249
+ public String getTransformation() { return transformation; }
250
+ public void setTransformation(String transformation) { this.transformation = transformation; }
251
+ public Double getConfidence() { return confidence; }
252
+ public void setConfidence(Double confidence) { this.confidence = confidence; }
253
+ }
254
+
255
+ /**
256
+ * DTO for edge statistics.
257
+ */
258
+ @JsonInclude(JsonInclude.Include.NON_NULL)
259
+ public static class EdgeStatsDTO {
260
+
261
+ private Long recordsProcessed;
262
+ private Double averageProcessingTime; // in milliseconds
263
+ private Integer executionCount;
264
+ private Instant lastSuccessfulExecution;
265
+ private Integer failureCount;
266
+ private String reliability; // HIGH, MEDIUM, LOW
267
+
268
+ public EdgeStatsDTO() {}
269
+
270
+ // Getters and setters
271
+ public Long getRecordsProcessed() { return recordsProcessed; }
272
+ public void setRecordsProcessed(Long recordsProcessed) { this.recordsProcessed = recordsProcessed; }
273
+ public Double getAverageProcessingTime() { return averageProcessingTime; }
274
+ public void setAverageProcessingTime(Double averageProcessingTime) { this.averageProcessingTime = averageProcessingTime; }
275
+ public Integer getExecutionCount() { return executionCount; }
276
+ public void setExecutionCount(Integer executionCount) { this.executionCount = executionCount; }
277
+ public Instant getLastSuccessfulExecution() { return lastSuccessfulExecution; }
278
+ public void setLastSuccessfulExecution(Instant lastSuccessfulExecution) { this.lastSuccessfulExecution = lastSuccessfulExecution; }
279
+ public Integer getFailureCount() { return failureCount; }
280
+ public void setFailureCount(Integer failureCount) { this.failureCount = failureCount; }
281
+ public String getReliability() { return reliability; }
282
+ public void setReliability(String reliability) { this.reliability = reliability; }
283
+ }
284
+
285
+ /**
286
+ * DTO for lineage statistics.
287
+ */
288
+ @JsonInclude(JsonInclude.Include.NON_NULL)
289
+ public static class LineageStatisticsDTO {
290
+
291
+ private Integer totalUpstreamAssets;
292
+ private Integer totalDownstreamAssets;
293
+ private Integer totalTransformations;
294
+ private Integer directDependencies;
295
+ private Integer indirectDependencies;
296
+ private Double lineageCompleteness; // 0.0 to 100.0
297
+ private String lineageHealth; // HEALTHY, PARTIALLY_TRACKED, INCOMPLETE, BROKEN
298
+ private Map<String, Integer> assetTypeBreakdown;
299
+ private Map<String, Integer> transformationTypeBreakdown;
300
+
301
+ public LineageStatisticsDTO() {}
302
+
303
+ // Getters and setters
304
+ public Integer getTotalUpstreamAssets() { return totalUpstreamAssets; }
305
+ public void setTotalUpstreamAssets(Integer totalUpstreamAssets) { this.totalUpstreamAssets = totalUpstreamAssets; }
306
+ public Integer getTotalDownstreamAssets() { return totalDownstreamAssets; }
307
+ public void setTotalDownstreamAssets(Integer totalDownstreamAssets) { this.totalDownstreamAssets = totalDownstreamAssets; }
308
+ public Integer getTotalTransformations() { return totalTransformations; }
309
+ public void setTotalTransformations(Integer totalTransformations) { this.totalTransformations = totalTransformations; }
310
+ public Integer getDirectDependencies() { return directDependencies; }
311
+ public void setDirectDependencies(Integer directDependencies) { this.directDependencies = directDependencies; }
312
+ public Integer getIndirectDependencies() { return indirectDependencies; }
313
+ public void setIndirectDependencies(Integer indirectDependencies) { this.indirectDependencies = indirectDependencies; }
314
+ public Double getLineageCompleteness() { return lineageCompleteness; }
315
+ public void setLineageCompleteness(Double lineageCompleteness) { this.lineageCompleteness = lineageCompleteness; }
316
+ public String getLineageHealth() { return lineageHealth; }
317
+ public void setLineageHealth(String lineageHealth) { this.lineageHealth = lineageHealth; }
318
+ public Map<String, Integer> getAssetTypeBreakdown() { return assetTypeBreakdown; }
319
+ public void setAssetTypeBreakdown(Map<String, Integer> assetTypeBreakdown) { this.assetTypeBreakdown = assetTypeBreakdown; }
320
+ public Map<String, Integer> getTransformationTypeBreakdown() { return transformationTypeBreakdown; }
321
+ public void setTransformationTypeBreakdown(Map<String, Integer> transformationTypeBreakdown) { this.transformationTypeBreakdown = transformationTypeBreakdown; }
322
+ }
323
+
324
+ /**
325
+ * DTO for critical lineage paths.
326
+ */
327
+ @JsonInclude(JsonInclude.Include.NON_NULL)
328
+ public static class LineagePathDTO {
329
+
330
+ private String pathId;
331
+ private String pathType; // CRITICAL_BUSINESS_PATH, HIGH_VOLUME_PATH, SENSITIVE_DATA_PATH
332
+ private List<UUID> nodeSequence;
333
+ private String description;
334
+ private String businessImpact; // HIGH, MEDIUM, LOW
335
+ private List<String> businessProcesses;
336
+ private String riskLevel; // CRITICAL, HIGH, MEDIUM, LOW
337
+ private Map<String, Object> metadata;
338
+
339
+ public LineagePathDTO() {}
340
+
341
+ public LineagePathDTO(String pathId, String pathType, List<UUID> nodeSequence) {
342
+ this.pathId = pathId;
343
+ this.pathType = pathType;
344
+ this.nodeSequence = nodeSequence;
345
+ }
346
+
347
+ // Getters and setters
348
+ public String getPathId() { return pathId; }
349
+ public void setPathId(String pathId) { this.pathId = pathId; }
350
+ public String getPathType() { return pathType; }
351
+ public void setPathType(String pathType) { this.pathType = pathType; }
352
+ public List<UUID> getNodeSequence() { return nodeSequence; }
353
+ public void setNodeSequence(List<UUID> nodeSequence) { this.nodeSequence = nodeSequence; }
354
+ public String getDescription() { return description; }
355
+ public void setDescription(String description) { this.description = description; }
356
+ public String getBusinessImpact() { return businessImpact; }
357
+ public void setBusinessImpact(String businessImpact) { this.businessImpact = businessImpact; }
358
+ public List<String> getBusinessProcesses() { return businessProcesses; }
359
+ public void setBusinessProcesses(List<String> businessProcesses) { this.businessProcesses = businessProcesses; }
360
+ public String getRiskLevel() { return riskLevel; }
361
+ public void setRiskLevel(String riskLevel) { this.riskLevel = riskLevel; }
362
+ public Map<String, Object> getMetadata() { return metadata; }
363
+ public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
364
+ }
365
+
366
+ /**
367
+ * DTO for lineage issues.
368
+ */
369
+ @JsonInclude(JsonInclude.Include.NON_NULL)
370
+ public static class LineageIssueDTO {
371
+
372
+ private String issueId;
373
+ private String issueType; // BROKEN_LINEAGE, MISSING_METADATA, STALE_CONNECTION, CIRCULAR_DEPENDENCY
374
+ private String severity; // CRITICAL, HIGH, MEDIUM, LOW
375
+ private String description;
376
+ private List<UUID> affectedNodes;
377
+ private String resolutionSuggestion;
378
+ private Instant detectedAt;
379
+ private String status; // OPEN, IN_PROGRESS, RESOLVED
380
+
381
+ public LineageIssueDTO() {}
382
+
383
+ public LineageIssueDTO(String issueId, String issueType, String severity) {
384
+ this.issueId = issueId;
385
+ this.issueType = issueType;
386
+ this.severity = severity;
387
+ }
388
+
389
+ // Getters and setters
390
+ public String getIssueId() { return issueId; }
391
+ public void setIssueId(String issueId) { this.issueId = issueId; }
392
+ public String getIssueType() { return issueType; }
393
+ public void setIssueType(String issueType) { this.issueType = issueType; }
394
+ public String getSeverity() { return severity; }
395
+ public void setSeverity(String severity) { this.severity = severity; }
396
+ public String getDescription() { return description; }
397
+ public void setDescription(String description) { this.description = description; }
398
+ public List<UUID> getAffectedNodes() { return affectedNodes; }
399
+ public void setAffectedNodes(List<UUID> affectedNodes) { this.affectedNodes = affectedNodes; }
400
+ public String getResolutionSuggestion() { return resolutionSuggestion; }
401
+ public void setResolutionSuggestion(String resolutionSuggestion) { this.resolutionSuggestion = resolutionSuggestion; }
402
+ public Instant getDetectedAt() { return detectedAt; }
403
+ public void setDetectedAt(Instant detectedAt) { this.detectedAt = detectedAt; }
404
+ public String getStatus() { return status; }
405
+ public void setStatus(String status) { this.status = status; }
406
+ }
407
+
408
+ /**
409
+ * DTO for visualization metadata.
410
+ */
411
+ @JsonInclude(JsonInclude.Include.NON_NULL)
412
+ public static class LineageVisualizationMetadataDTO {
413
+
414
+ private String layoutType; // HIERARCHICAL, FORCE_DIRECTED, CIRCULAR, TREE
415
+ private Map<String, Object> layoutConfiguration;
416
+ private Map<String, String> colorScheme; // nodeType -> color
417
+ private Map<String, String> iconMapping; // nodeType -> icon
418
+ private List<String> availableFilters;
419
+ private Map<String, Object> defaultViewport; // Initial zoom and pan settings
420
+ private Boolean supportsInteractiveEditing;
421
+
422
+ public LineageVisualizationMetadataDTO() {}
423
+
424
+ // Getters and setters
425
+ public String getLayoutType() { return layoutType; }
426
+ public void setLayoutType(String layoutType) { this.layoutType = layoutType; }
427
+ public Map<String, Object> getLayoutConfiguration() { return layoutConfiguration; }
428
+ public void setLayoutConfiguration(Map<String, Object> layoutConfiguration) { this.layoutConfiguration = layoutConfiguration; }
429
+ public Map<String, String> getColorScheme() { return colorScheme; }
430
+ public void setColorScheme(Map<String, String> colorScheme) { this.colorScheme = colorScheme; }
431
+ public Map<String, String> getIconMapping() { return iconMapping; }
432
+ public void setIconMapping(Map<String, String> iconMapping) { this.iconMapping = iconMapping; }
433
+ public List<String> getAvailableFilters() { return availableFilters; }
434
+ public void setAvailableFilters(List<String> availableFilters) { this.availableFilters = availableFilters; }
435
+ public Map<String, Object> getDefaultViewport() { return defaultViewport; }
436
+ public void setDefaultViewport(Map<String, Object> defaultViewport) { this.defaultViewport = defaultViewport; }
437
+ public Boolean getSupportsInteractiveEditing() { return supportsInteractiveEditing; }
438
+ public void setSupportsInteractiveEditing(Boolean supportsInteractiveEditing) { this.supportsInteractiveEditing = supportsInteractiveEditing; }
439
+ }
440
+
441
+ // Main class getters and setters
442
+ public UUID getAssetId() { return assetId; }
443
+ public void setAssetId(UUID assetId) { this.assetId = assetId; }
444
+ public String getAssetName() { return assetName; }
445
+ public void setAssetName(String assetName) { this.assetName = assetName; }
446
+ public LineageGraphDTO getLineageGraph() { return lineageGraph; }
447
+ public void setLineageGraph(LineageGraphDTO lineageGraph) { this.lineageGraph = lineageGraph; }
448
+ public LineageStatisticsDTO getStatistics() { return statistics; }
449
+ public void setStatistics(LineageStatisticsDTO statistics) { this.statistics = statistics; }
450
+ public List<LineagePathDTO> getCriticalPaths() { return criticalPaths; }
451
+ public void setCriticalPaths(List<LineagePathDTO> criticalPaths) { this.criticalPaths = criticalPaths; }
452
+ public List<LineageIssueDTO> getIssues() { return issues; }
453
+ public void setIssues(List<LineageIssueDTO> issues) { this.issues = issues; }
454
+ public LineageVisualizationMetadataDTO getVisualizationMetadata() { return visualizationMetadata; }
455
+ public void setVisualizationMetadata(LineageVisualizationMetadataDTO visualizationMetadata) { this.visualizationMetadata = visualizationMetadata; }
456
+ public Instant getLastUpdated() { return lastUpdated; }
457
+ public void setLastUpdated(Instant lastUpdated) { this.lastUpdated = lastUpdated; }
458
+ public String getLineageVersion() { return lineageVersion; }
459
+ public void setLineageVersion(String lineageVersion) { this.lineageVersion = lineageVersion; }
460
+ public Integer getMaxDepth() { return maxDepth; }
461
+ public void setMaxDepth(Integer maxDepth) { this.maxDepth = maxDepth; }
462
+ }
src/main/java/com/dalab/catalog/dto/LabelAssignmentInputDTO.java ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.UUID;
4
+
5
+ // DTO for assigning a single label
6
+ public class LabelAssignmentInputDTO {
7
+ // Allow providing either labelName or labelId to identify the label
8
+ private String labelName;
9
+ private UUID labelId;
10
+
11
+ private Double confidenceScore; // Optional
12
+ private String source; // Optional, e.g., "USER_PROVIDED", "RULE_BASED"
13
+
14
+ // Getters and Setters
15
+ public String getLabelName() {
16
+ return labelName;
17
+ }
18
+
19
+ public void setLabelName(String labelName) {
20
+ this.labelName = labelName;
21
+ }
22
+
23
+ public UUID getLabelId() {
24
+ return labelId;
25
+ }
26
+
27
+ public void setLabelId(UUID labelId) {
28
+ this.labelId = labelId;
29
+ }
30
+
31
+ public Double getConfidenceScore() {
32
+ return confidenceScore;
33
+ }
34
+
35
+ public void setConfidenceScore(Double confidenceScore) {
36
+ this.confidenceScore = confidenceScore;
37
+ }
38
+
39
+ public String getSource() {
40
+ return source;
41
+ }
42
+
43
+ public void setSource(String source) {
44
+ this.source = source;
45
+ }
46
+ }
src/main/java/com/dalab/catalog/dto/LabelOutputDTO.java ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.UUID;
5
+
6
+ import com.fasterxml.jackson.annotation.JsonInclude;
7
+
8
+ @JsonInclude(JsonInclude.Include.NON_NULL)
9
+ public class LabelOutputDTO {
10
+ // Fields from Label entity
11
+ private UUID labelId;
12
+ private String labelName;
13
+ private String labelCategory;
14
+ private String description;
15
+ // private UUID labelCreatedByUserId; // If needed to distinguish from assignment user
16
+ // private Instant labelCreatedAt;
17
+ // private Instant labelUpdatedAt;
18
+
19
+ // Fields from AssetLabelAssignment entity
20
+ private UUID assignedByUserId; // User who assigned this label to the asset
21
+ private Instant assignedAt; // When this specific assignment was made
22
+ private Double confidenceScore;
23
+ private String source; // Source of this specific assignment
24
+
25
+ public LabelOutputDTO() {}
26
+
27
+ // Getters and Setters
28
+ public UUID getLabelId() {
29
+ return labelId;
30
+ }
31
+
32
+ public void setLabelId(UUID labelId) {
33
+ this.labelId = labelId;
34
+ }
35
+
36
+ public String getLabelName() {
37
+ return labelName;
38
+ }
39
+
40
+ public void setLabelName(String labelName) {
41
+ this.labelName = labelName;
42
+ }
43
+
44
+ public String getLabelCategory() {
45
+ return labelCategory;
46
+ }
47
+
48
+ public void setLabelCategory(String labelCategory) {
49
+ this.labelCategory = labelCategory;
50
+ }
51
+
52
+ public String getDescription() {
53
+ return description;
54
+ }
55
+
56
+ public void setDescription(String description) {
57
+ this.description = description;
58
+ }
59
+
60
+ public UUID getAssignedByUserId() {
61
+ return assignedByUserId;
62
+ }
63
+
64
+ public void setAssignedByUserId(UUID assignedByUserId) {
65
+ this.assignedByUserId = assignedByUserId;
66
+ }
67
+
68
+ public Instant getAssignedAt() {
69
+ return assignedAt;
70
+ }
71
+
72
+ public void setAssignedAt(Instant assignedAt) {
73
+ this.assignedAt = assignedAt;
74
+ }
75
+
76
+ public Double getConfidenceScore() {
77
+ return confidenceScore;
78
+ }
79
+
80
+ public void setConfidenceScore(Double confidenceScore) {
81
+ this.confidenceScore = confidenceScore;
82
+ }
83
+
84
+ public String getSource() {
85
+ return source;
86
+ }
87
+
88
+ public void setSource(String source) {
89
+ this.source = source;
90
+ }
91
+ }
src/main/java/com/dalab/catalog/dto/LineageAssetDTO.java ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.UUID;
4
+
5
+ public class LineageAssetDTO {
6
+ private UUID assetId;
7
+ private String assetName;
8
+ private String assetType;
9
+ private String cloudProvider;
10
+ // Add relationshipType if showing direct relationship context in this DTO
11
+ // private String relationshipType;
12
+
13
+ public LineageAssetDTO() {}
14
+
15
+ public LineageAssetDTO(UUID assetId, String assetName, String assetType, String cloudProvider) {
16
+ this.assetId = assetId;
17
+ this.assetName = assetName;
18
+ this.assetType = assetType;
19
+ this.cloudProvider = cloudProvider;
20
+ }
21
+
22
+ // Getters and Setters
23
+ public UUID getAssetId() {
24
+ return assetId;
25
+ }
26
+
27
+ public void setAssetId(UUID assetId) {
28
+ this.assetId = assetId;
29
+ }
30
+
31
+ public String getAssetName() {
32
+ return assetName;
33
+ }
34
+
35
+ public void setAssetName(String assetName) {
36
+ this.assetName = assetName;
37
+ }
38
+
39
+ public String getAssetType() {
40
+ return assetType;
41
+ }
42
+
43
+ public void setAssetType(String assetType) {
44
+ this.assetType = assetType;
45
+ }
46
+
47
+ public String getCloudProvider() {
48
+ return cloudProvider;
49
+ }
50
+
51
+ public void setCloudProvider(String cloudProvider) {
52
+ this.cloudProvider = cloudProvider;
53
+ }
54
+ }
src/main/java/com/dalab/catalog/dto/LineageResponseDTO.java ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.List;
4
+
5
+ public class LineageResponseDTO {
6
+ private List<LineageAssetDTO> upstreamAssets;
7
+ private List<LineageAssetDTO> downstreamAssets;
8
+
9
+ public LineageResponseDTO() {}
10
+
11
+ public LineageResponseDTO(List<LineageAssetDTO> upstreamAssets, List<LineageAssetDTO> downstreamAssets) {
12
+ this.upstreamAssets = upstreamAssets;
13
+ this.downstreamAssets = downstreamAssets;
14
+ }
15
+
16
+ // Getters and Setters
17
+ public List<LineageAssetDTO> getUpstreamAssets() {
18
+ return upstreamAssets;
19
+ }
20
+
21
+ public void setUpstreamAssets(List<LineageAssetDTO> upstreamAssets) {
22
+ this.upstreamAssets = upstreamAssets;
23
+ }
24
+
25
+ public List<LineageAssetDTO> getDownstreamAssets() {
26
+ return downstreamAssets;
27
+ }
28
+
29
+ public void setDownstreamAssets(List<LineageAssetDTO> downstreamAssets) {
30
+ this.downstreamAssets = downstreamAssets;
31
+ }
32
+ }
src/main/java/com/dalab/catalog/dto/TaxonomyLabelDTO.java ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.time.Instant;
4
+ import java.util.UUID;
5
+
6
+ import com.fasterxml.jackson.annotation.JsonInclude;
7
+
8
+ @JsonInclude(JsonInclude.Include.NON_NULL)
9
+ public class TaxonomyLabelDTO {
10
+ private UUID labelId;
11
+ private String labelName;
12
+ private String labelCategory;
13
+ private String description;
14
+ private UUID createdByUserId; // User who created the label definition
15
+ private Instant createdAt;
16
+ private Instant updatedAt;
17
+
18
+ public TaxonomyLabelDTO() {}
19
+
20
+ // Getters and Setters
21
+ public UUID getLabelId() {
22
+ return labelId;
23
+ }
24
+
25
+ public void setLabelId(UUID labelId) {
26
+ this.labelId = labelId;
27
+ }
28
+
29
+ public String getLabelName() {
30
+ return labelName;
31
+ }
32
+
33
+ public void setLabelName(String labelName) {
34
+ this.labelName = labelName;
35
+ }
36
+
37
+ public String getLabelCategory() {
38
+ return labelCategory;
39
+ }
40
+
41
+ public void setLabelCategory(String labelCategory) {
42
+ this.labelCategory = labelCategory;
43
+ }
44
+
45
+ public String getDescription() {
46
+ return description;
47
+ }
48
+
49
+ public void setDescription(String description) {
50
+ this.description = description;
51
+ }
52
+
53
+ public UUID getCreatedByUserId() {
54
+ return createdByUserId;
55
+ }
56
+
57
+ public void setCreatedByUserId(UUID createdByUserId) {
58
+ this.createdByUserId = createdByUserId;
59
+ }
60
+
61
+ public Instant getCreatedAt() {
62
+ return createdAt;
63
+ }
64
+
65
+ public void setCreatedAt(Instant createdAt) {
66
+ this.createdAt = createdAt;
67
+ }
68
+
69
+ public Instant getUpdatedAt() {
70
+ return updatedAt;
71
+ }
72
+
73
+ public void setUpdatedAt(Instant updatedAt) {
74
+ this.updatedAt = updatedAt;
75
+ }
76
+ }
src/main/java/com/dalab/catalog/dto/TaxonomyLabelsResponseDTO.java ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.dto;
2
+
3
+ import java.util.List;
4
+
5
+ public class TaxonomyLabelsResponseDTO {
6
+ private List<TaxonomyLabelDTO> labels;
7
+
8
+ public TaxonomyLabelsResponseDTO() {}
9
+
10
+ public TaxonomyLabelsResponseDTO(List<TaxonomyLabelDTO> labels) {
11
+ this.labels = labels;
12
+ }
13
+
14
+ public List<TaxonomyLabelDTO> getLabels() {
15
+ return labels;
16
+ }
17
+
18
+ public void setLabels(List<TaxonomyLabelDTO> labels) {
19
+ this.labels = labels;
20
+ }
21
+ }
src/main/java/com/dalab/catalog/mapper/AssetMapper.java ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.mapper;
2
+
3
+ import java.util.Collections;
4
+ import java.util.List;
5
+ import java.util.stream.Collectors;
6
+
7
+ import org.springframework.beans.factory.annotation.Autowired;
8
+ import org.springframework.stereotype.Component;
9
+
10
+ import com.dalab.catalog.dto.AssetOutputDTO;
11
+ import com.dalab.common.model.entity.Asset;
12
+ import com.dalab.common.repository.IAssetLabelAssignmentRepository;
13
+
14
+ @Component
15
+ public class AssetMapper {
16
+
17
+ private final IAssetLabelAssignmentRepository assetLabelAssignmentRepository;
18
+
19
+ @Autowired
20
+ public AssetMapper(IAssetLabelAssignmentRepository assetLabelAssignmentRepository) {
21
+ this.assetLabelAssignmentRepository = assetLabelAssignmentRepository;
22
+ }
23
+
24
+ public AssetOutputDTO toAssetOutputDTO(Asset asset) {
25
+ if (asset == null) {
26
+ return null;
27
+ }
28
+
29
+ AssetOutputDTO dto = new AssetOutputDTO();
30
+ dto.setAssetId(asset.getAssetId());
31
+ dto.setAssetName(asset.getAssetName());
32
+ dto.setCloudConnectionId(asset.getCloudConnectionId());
33
+ dto.setCloudProvider(asset.getCloudProvider());
34
+ dto.setAssetType(asset.getAssetType());
35
+ dto.setNativeAssetId(asset.getNativeAssetId());
36
+ dto.setRegion(asset.getRegion());
37
+ dto.setDetailsJson(asset.getDetailsJson());
38
+ dto.setDiscoveredAt(asset.getDiscoveredAt());
39
+ dto.setLastScannedAt(asset.getLastScannedAt());
40
+ dto.setCreatedByUserId(asset.getCreatedByUserId());
41
+ dto.setCreatedAt(asset.getCreatedTs());
42
+ dto.setUpdatedAt(asset.getLastModifiedTs());
43
+ dto.setBusinessMetadataJson(asset.getBusinessMetadataJson());
44
+
45
+ // TODO: Implement label names retrieval
46
+ // For now, return empty list to avoid compilation errors
47
+ dto.setLabels(Collections.emptyList());
48
+
49
+ return dto;
50
+ }
51
+
52
+ public List<AssetOutputDTO> toAssetOutputDTOList(List<Asset> assets) {
53
+ if (assets == null) {
54
+ return Collections.emptyList();
55
+ }
56
+ return assets.stream()
57
+ .map(this::toAssetOutputDTO)
58
+ .collect(Collectors.toList());
59
+ }
60
+ }
src/main/java/com/dalab/catalog/mapper/LabelTaxonomyMapper.java ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.mapper;
2
+
3
+ import org.springframework.stereotype.Component;
4
+
5
+ import com.dalab.catalog.dto.TaxonomyLabelDTO;
6
+ import com.dalab.common.model.entity.Label;
7
+
8
+ @Component
9
+ public class LabelTaxonomyMapper {
10
+
11
+ public TaxonomyLabelDTO toTaxonomyLabelDTO(Label label) {
12
+ if (label == null) {
13
+ return null;
14
+ }
15
+ TaxonomyLabelDTO dto = new TaxonomyLabelDTO();
16
+ dto.setLabelId(label.getLabelId());
17
+ dto.setLabelName(label.getLabelName());
18
+ dto.setLabelCategory(label.getLabelCategory());
19
+ dto.setDescription(label.getDescription());
20
+ dto.setCreatedByUserId(label.getCreatedByUserId());
21
+ dto.setCreatedAt(label.getCreatedTs());
22
+ dto.setUpdatedAt(label.getLastModifiedTs());
23
+ return dto;
24
+ }
25
+
26
+ public Label toLabelEntity(TaxonomyLabelDTO dto) {
27
+ if (dto == null) {
28
+ return null;
29
+ }
30
+ Label label = new Label();
31
+ // label.setLabelId(dto.getLabelId()); // ID is usually set on creation or fetched
32
+ label.setLabelName(dto.getLabelName());
33
+ label.setLabelCategory(dto.getLabelCategory());
34
+ label.setDescription(dto.getDescription());
35
+ // createdByUserId, createdAt, updatedAt are often set by system or @PrePersist/@PreUpdate
36
+ return label;
37
+ }
38
+
39
+ public void updateLabelEntityFromDto(Label label, TaxonomyLabelDTO dto) {
40
+ if (dto == null || label == null) {
41
+ return;
42
+ }
43
+ if (dto.getLabelName() != null) {
44
+ label.setLabelName(dto.getLabelName());
45
+ }
46
+ if (dto.getLabelCategory() != null) {
47
+ label.setLabelCategory(dto.getLabelCategory());
48
+ }
49
+ if (dto.getDescription() != null) {
50
+ label.setDescription(dto.getDescription());
51
+ }
52
+ // createdByUserId is usually not updated this way
53
+ }
54
+ }
src/main/java/com/dalab/catalog/model/Asset.java ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.model;
2
+
3
+ import java.time.Instant;
4
+ import java.util.Map;
5
+ import java.util.UUID;
6
+
7
+ import org.hibernate.annotations.JdbcTypeCode;
8
+ import org.hibernate.type.SqlTypes;
9
+
10
+ import jakarta.persistence.*;
11
+
12
+ @Entity
13
+ @Table(name = "dalab_assets", schema = "dalab_catalog",
14
+ indexes = {
15
+ @Index(name = "idx_asset_cloud_connection_id", columnList = "cloudConnectionId"),
16
+ @Index(name = "idx_asset_asset_type", columnList = "assetType"),
17
+ @Index(name = "idx_asset_native_asset_id", columnList = "nativeAssetId", unique = true) // Assuming native ID should be unique
18
+ })
19
+ public class Asset {
20
+
21
+ @Id
22
+ private UUID assetId;
23
+
24
+ @Column(nullable = false)
25
+ private String assetName;
26
+
27
+ @Column(nullable = false)
28
+ private UUID cloudConnectionId; // Links to the connection in da-admin-service
29
+
30
+ @Column(nullable = false)
31
+ private String cloudProvider; // e.g., GCP, AWS, AZURE, OCI
32
+
33
+ @Column(nullable = false)
34
+ private String assetType; // e.g., "BigQuery Dataset", "S3 Bucket", "Azure Blob Container"
35
+
36
+ @Column(nullable = false, length = 1024) // Native ID can be long
37
+ private String nativeAssetId;
38
+
39
+ @Column
40
+ private String region;
41
+
42
+ @Lob // For potentially large JSON strings, though JSONB is usually better handled by @JdbcTypeCode
43
+ @Column(columnDefinition = "jsonb")
44
+ @JdbcTypeCode(SqlTypes.JSON)
45
+ private Map<String, Object> detailsJson; // Provider-specific metadata
46
+
47
+ @Column(columnDefinition = "jsonb")
48
+ @JdbcTypeCode(SqlTypes.JSON)
49
+ private Map<String, Object> businessMetadataJson; // Business-specific, user-editable metadata
50
+
51
+ private Instant discoveredAt; // Timestamp of first discovery
52
+ private Instant lastScannedAt; // Timestamp of the last scan that updated this asset
53
+
54
+ // @ManyToOne(fetch = FetchType.LAZY) // Assuming dalab_users table is defined elsewhere or by another service
55
+ // @JoinColumn(name = "created_by_user_id")
56
+ // private User createdByUser; // If User entity is part of this service
57
+ private UUID createdByUserId; // Simpler: store user ID directly
58
+
59
+ @Column(nullable = false, updatable = false)
60
+ private Instant createdAt;
61
+
62
+ @Column(nullable = false)
63
+ private Instant updatedAt;
64
+
65
+ public Asset() {
66
+ this.assetId = UUID.randomUUID();
67
+ }
68
+
69
+ @PrePersist
70
+ protected void onCreate() {
71
+ createdAt = updatedAt = Instant.now();
72
+ }
73
+
74
+ @PreUpdate
75
+ protected void onUpdate() {
76
+ updatedAt = Instant.now();
77
+ }
78
+
79
+ // Getters and Setters
80
+ public UUID getAssetId() {
81
+ return assetId;
82
+ }
83
+
84
+ public void setAssetId(UUID assetId) {
85
+ this.assetId = assetId;
86
+ }
87
+
88
+ public String getAssetName() {
89
+ return assetName;
90
+ }
91
+
92
+ public void setAssetName(String assetName) {
93
+ this.assetName = assetName;
94
+ }
95
+
96
+ public UUID getCloudConnectionId() {
97
+ return cloudConnectionId;
98
+ }
99
+
100
+ public void setCloudConnectionId(UUID cloudConnectionId) {
101
+ this.cloudConnectionId = cloudConnectionId;
102
+ }
103
+
104
+ public String getCloudProvider() {
105
+ return cloudProvider;
106
+ }
107
+
108
+ public void setCloudProvider(String cloudProvider) {
109
+ this.cloudProvider = cloudProvider;
110
+ }
111
+
112
+ public String getAssetType() {
113
+ return assetType;
114
+ }
115
+
116
+ public void setAssetType(String assetType) {
117
+ this.assetType = assetType;
118
+ }
119
+
120
+ public String getNativeAssetId() {
121
+ return nativeAssetId;
122
+ }
123
+
124
+ public void setNativeAssetId(String nativeAssetId) {
125
+ this.nativeAssetId = nativeAssetId;
126
+ }
127
+
128
+ public String getRegion() {
129
+ return region;
130
+ }
131
+
132
+ public void setRegion(String region) {
133
+ this.region = region;
134
+ }
135
+
136
+ public Map<String, Object> getDetailsJson() {
137
+ return detailsJson;
138
+ }
139
+
140
+ public void setDetailsJson(Map<String, Object> detailsJson) {
141
+ this.detailsJson = detailsJson;
142
+ }
143
+
144
+ public Map<String, Object> getBusinessMetadataJson() {
145
+ return businessMetadataJson;
146
+ }
147
+
148
+ public void setBusinessMetadataJson(Map<String, Object> businessMetadataJson) {
149
+ this.businessMetadataJson = businessMetadataJson;
150
+ }
151
+
152
+ public Instant getDiscoveredAt() {
153
+ return discoveredAt;
154
+ }
155
+
156
+ public void setDiscoveredAt(Instant discoveredAt) {
157
+ this.discoveredAt = discoveredAt;
158
+ }
159
+
160
+ public Instant getLastScannedAt() {
161
+ return lastScannedAt;
162
+ }
163
+
164
+ public void setLastScannedAt(Instant lastScannedAt) {
165
+ this.lastScannedAt = lastScannedAt;
166
+ }
167
+
168
+ public UUID getCreatedByUserId() {
169
+ return createdByUserId;
170
+ }
171
+
172
+ public void setCreatedByUserId(UUID createdByUserId) {
173
+ this.createdByUserId = createdByUserId;
174
+ }
175
+
176
+ public Instant getCreatedAt() {
177
+ return createdAt;
178
+ }
179
+
180
+ public void setCreatedAt(Instant createdAt) {
181
+ this.createdAt = createdAt;
182
+ }
183
+
184
+ public Instant getUpdatedAt() {
185
+ return updatedAt;
186
+ }
187
+
188
+ public void setUpdatedAt(Instant updatedAt) {
189
+ this.updatedAt = updatedAt;
190
+ }
191
+ }
src/main/java/com/dalab/catalog/model/AssetLineage.java ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.model;
2
+
3
+ import java.time.Instant;
4
+ import java.util.Map;
5
+ import java.util.UUID;
6
+
7
+ import org.hibernate.annotations.JdbcTypeCode;
8
+ import org.hibernate.type.SqlTypes;
9
+
10
+ import com.dalab.common.model.entity.Asset;
11
+
12
+ import jakarta.persistence.Column;
13
+ import jakarta.persistence.Entity;
14
+ import jakarta.persistence.FetchType;
15
+ import jakarta.persistence.Id;
16
+ import jakarta.persistence.Index;
17
+ import jakarta.persistence.JoinColumn;
18
+ import jakarta.persistence.ManyToOne;
19
+ import jakarta.persistence.PrePersist;
20
+ import jakarta.persistence.Table;
21
+
22
+ @Entity
23
+ @Table(name = "dalab_asset_lineage", schema = "dalab_catalog",
24
+ indexes = {
25
+ @Index(name = "idx_lineage_upstream_asset_id", columnList = "upstream_asset_id"),
26
+ @Index(name = "idx_lineage_downstream_asset_id", columnList = "downstream_asset_id")
27
+ })
28
+ public class AssetLineage {
29
+
30
+ @Id
31
+ private UUID lineageId;
32
+
33
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
34
+ @JoinColumn(name = "upstream_asset_id", nullable = false)
35
+ private Asset upstreamAsset;
36
+
37
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
38
+ @JoinColumn(name = "downstream_asset_id", nullable = false)
39
+ private Asset downstreamAsset;
40
+
41
+ @Column(length = 100)
42
+ private String relationshipType; // e.g., "TRANSFORMED_FROM", "COPIED_FROM", "VIEW_OF"
43
+
44
+ @Column(columnDefinition = "jsonb")
45
+ @JdbcTypeCode(SqlTypes.JSON)
46
+ private Map<String, Object> detailsJson; // e.g., job name, transformation logic snippet
47
+
48
+ private UUID createdByUserId;
49
+
50
+ @Column(nullable = false, updatable = false)
51
+ private Instant createdAt;
52
+
53
+ public AssetLineage() {
54
+ this.lineageId = UUID.randomUUID();
55
+ }
56
+
57
+ @PrePersist
58
+ protected void onCreate() {
59
+ createdAt = Instant.now();
60
+ }
61
+
62
+ // Getters and Setters
63
+ public UUID getLineageId() {
64
+ return lineageId;
65
+ }
66
+
67
+ public void setLineageId(UUID lineageId) {
68
+ this.lineageId = lineageId;
69
+ }
70
+
71
+ public Asset getUpstreamAsset() {
72
+ return upstreamAsset;
73
+ }
74
+
75
+ public void setUpstreamAsset(Asset upstreamAsset) {
76
+ this.upstreamAsset = upstreamAsset;
77
+ }
78
+
79
+ public Asset getDownstreamAsset() {
80
+ return downstreamAsset;
81
+ }
82
+
83
+ public void setDownstreamAsset(Asset downstreamAsset) {
84
+ this.downstreamAsset = downstreamAsset;
85
+ }
86
+
87
+ public String getRelationshipType() {
88
+ return relationshipType;
89
+ }
90
+
91
+ public void setRelationshipType(String relationshipType) {
92
+ this.relationshipType = relationshipType;
93
+ }
94
+
95
+ public Map<String, Object> getDetailsJson() {
96
+ return detailsJson;
97
+ }
98
+
99
+ public void setDetailsJson(Map<String, Object> detailsJson) {
100
+ this.detailsJson = detailsJson;
101
+ }
102
+
103
+ public UUID getCreatedByUserId() {
104
+ return createdByUserId;
105
+ }
106
+
107
+ public void setCreatedByUserId(UUID createdByUserId) {
108
+ this.createdByUserId = createdByUserId;
109
+ }
110
+
111
+ public Instant getCreatedAt() {
112
+ return createdAt;
113
+ }
114
+
115
+ public void setCreatedAt(Instant createdAt) {
116
+ this.createdAt = createdAt;
117
+ }
118
+ }
src/main/java/com/dalab/catalog/model/Label.java ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.model;
2
+
3
+ import java.time.Instant;
4
+ import java.util.UUID;
5
+
6
+ import jakarta.persistence.*;
7
+
8
+ @Entity
9
+ @Table(name = "dalab_labels", schema = "dalab_catalog",
10
+ indexes = {
11
+ @Index(name = "idx_label_name", columnList = "labelName", unique = true),
12
+ @Index(name = "idx_label_category", columnList = "labelCategory")
13
+ })
14
+ public class Label {
15
+
16
+ @Id
17
+ private UUID labelId;
18
+
19
+ @Column(nullable = false, unique = true)
20
+ private String labelName;
21
+
22
+ @Column
23
+ private String labelCategory; // e.g., "Sensitivity", "Classification", "Custom", "DataDomain"
24
+
25
+ @Column(columnDefinition = "TEXT")
26
+ private String description;
27
+
28
+ // private UUID createdByUserId; // Link to User if needed
29
+ // As per schema, user who created the label definition
30
+
31
+ @Column(nullable = false, updatable = false)
32
+ private Instant createdAt;
33
+
34
+ @Column(nullable = false)
35
+ private Instant updatedAt;
36
+
37
+ // Consider adding createdByUserId as in schema
38
+ private UUID createdByUserId;
39
+
40
+ public Label() {
41
+ this.labelId = UUID.randomUUID();
42
+ }
43
+
44
+ @PrePersist
45
+ protected void onCreate() {
46
+ createdAt = updatedAt = Instant.now();
47
+ }
48
+
49
+ @PreUpdate
50
+ protected void onUpdate() {
51
+ updatedAt = Instant.now();
52
+ }
53
+
54
+ // Getters and Setters
55
+ public UUID getLabelId() {
56
+ return labelId;
57
+ }
58
+
59
+ public void setLabelId(UUID labelId) {
60
+ this.labelId = labelId;
61
+ }
62
+
63
+ public String getLabelName() {
64
+ return labelName;
65
+ }
66
+
67
+ public void setLabelName(String labelName) {
68
+ this.labelName = labelName;
69
+ }
70
+
71
+ public String getLabelCategory() {
72
+ return labelCategory;
73
+ }
74
+
75
+ public void setLabelCategory(String labelCategory) {
76
+ this.labelCategory = labelCategory;
77
+ }
78
+
79
+ public String getDescription() {
80
+ return description;
81
+ }
82
+
83
+ public void setDescription(String description) {
84
+ this.description = description;
85
+ }
86
+
87
+ public Instant getCreatedAt() {
88
+ return createdAt;
89
+ }
90
+
91
+ public void setCreatedAt(Instant createdAt) {
92
+ this.createdAt = createdAt;
93
+ }
94
+
95
+ public Instant getUpdatedAt() {
96
+ return updatedAt;
97
+ }
98
+
99
+ public void setUpdatedAt(Instant updatedAt) {
100
+ this.updatedAt = updatedAt;
101
+ }
102
+
103
+ public UUID getCreatedByUserId() {
104
+ return createdByUserId;
105
+ }
106
+
107
+ public void setCreatedByUserId(UUID createdByUserId) {
108
+ this.createdByUserId = createdByUserId;
109
+ }
110
+ }
src/main/java/com/dalab/catalog/repository/AssetLineageRepository.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.repository;
2
+
3
+ import java.util.List;
4
+ import java.util.UUID;
5
+
6
+ import org.springframework.data.jpa.repository.JpaRepository;
7
+ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
8
+ import org.springframework.stereotype.Repository;
9
+
10
+ import com.dalab.catalog.model.AssetLineage;
11
+
12
+ @Repository
13
+ public interface AssetLineageRepository extends JpaRepository<AssetLineage, UUID>, JpaSpecificationExecutor<AssetLineage> {
14
+
15
+ // Find all lineage records where the given asset is downstream (i.e., find its direct upstream assets)
16
+ List<AssetLineage> findByDownstreamAsset_AssetId(UUID downstreamAssetId);
17
+
18
+ // Find all lineage records where the given asset is upstream (i.e., find its direct downstream assets)
19
+ List<AssetLineage> findByUpstreamAsset_AssetId(UUID upstreamAssetId);
20
+
21
+ // For deleting lineage when an asset is deleted (cascade might be handled by DB constraints too)
22
+ void deleteByUpstreamAsset_AssetIdOrDownstreamAsset_AssetId(UUID upstreamAssetId, UUID downstreamAssetId);
23
+ }
src/main/java/com/dalab/catalog/security/SecurityUtils.java ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.security;
2
+
3
+ import java.util.UUID;
4
+
5
+ import org.slf4j.Logger;
6
+ import org.slf4j.LoggerFactory;
7
+ import org.springframework.security.core.Authentication;
8
+ import org.springframework.security.core.context.SecurityContextHolder;
9
+ import org.springframework.security.oauth2.jwt.Jwt;
10
+ import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
11
+
12
+ /**
13
+ * Utility class for Spring Security operations related to authentication and authorization.
14
+ * Handles extraction of user information from JWT tokens issued by Keycloak.
15
+ */
16
+ public final class SecurityUtils {
17
+
18
+ private static final Logger log = LoggerFactory.getLogger(SecurityUtils.class);
19
+
20
+ private static final String SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
21
+ private static final String USER_ID_CLAIM = "sub"; // Standard JWT subject claim
22
+ private static final String PREFERRED_USERNAME_CLAIM = "preferred_username";
23
+
24
+ private SecurityUtils() {
25
+ // Utility class
26
+ }
27
+
28
+ /**
29
+ * Get the authenticated user ID from the Spring Security context.
30
+ * Extracts the user ID from the JWT token's 'sub' claim.
31
+ *
32
+ * @return UUID of the authenticated user, or system user ID if not authenticated
33
+ */
34
+ public static UUID getAuthenticatedUserId() {
35
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
36
+
37
+ if (authentication == null) {
38
+ log.warn("No authentication found in security context, returning system user ID");
39
+ return UUID.fromString(SYSTEM_USER_ID);
40
+ }
41
+
42
+ if (authentication instanceof JwtAuthenticationToken jwtToken) {
43
+ Jwt jwt = jwtToken.getToken();
44
+ String userIdClaim = jwt.getClaimAsString(USER_ID_CLAIM);
45
+
46
+ if (userIdClaim != null) {
47
+ try {
48
+ return UUID.fromString(userIdClaim);
49
+ } catch (IllegalArgumentException e) {
50
+ log.error("Invalid UUID format in JWT sub claim: {}", userIdClaim, e);
51
+ return UUID.fromString(SYSTEM_USER_ID);
52
+ }
53
+ } else {
54
+ log.warn("No 'sub' claim found in JWT token, returning system user ID");
55
+ return UUID.fromString(SYSTEM_USER_ID);
56
+ }
57
+ }
58
+
59
+ // For non-JWT authentication (e.g., system calls, service-to-service)
60
+ log.debug("Non-JWT authentication found: {}, returning system user ID", authentication.getClass().getSimpleName());
61
+ return UUID.fromString(SYSTEM_USER_ID);
62
+ }
63
+
64
+ /**
65
+ * Get the authenticated username from the Spring Security context.
66
+ *
67
+ * @return Username of the authenticated user, or "system" if not authenticated
68
+ */
69
+ public static String getAuthenticatedUsername() {
70
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
71
+
72
+ if (authentication == null) {
73
+ return "system";
74
+ }
75
+
76
+ if (authentication instanceof JwtAuthenticationToken jwtToken) {
77
+ Jwt jwt = jwtToken.getToken();
78
+ String username = jwt.getClaimAsString(PREFERRED_USERNAME_CLAIM);
79
+ if (username != null) {
80
+ return username;
81
+ }
82
+ // Fallback to subject if preferred_username not available
83
+ return jwt.getClaimAsString(USER_ID_CLAIM);
84
+ }
85
+
86
+ return authentication.getName() != null ? authentication.getName() : "system";
87
+ }
88
+
89
+ /**
90
+ * Check if the current user has a specific authority/role.
91
+ *
92
+ * @param authority The authority to check for
93
+ * @return true if the user has the authority, false otherwise
94
+ */
95
+ public static boolean hasAuthority(String authority) {
96
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
97
+ if (authentication == null) {
98
+ return false;
99
+ }
100
+
101
+ return authentication.getAuthorities().stream()
102
+ .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority));
103
+ }
104
+
105
+ /**
106
+ * Check if the current authentication represents a system user (non-human).
107
+ * This is useful for service-to-service calls or system-initiated operations.
108
+ *
109
+ * @return true if this is a system user, false otherwise
110
+ */
111
+ public static boolean isSystemUser() {
112
+ UUID userId = getAuthenticatedUserId();
113
+ return SYSTEM_USER_ID.equals(userId.toString());
114
+ }
115
+ }
src/main/java/com/dalab/catalog/service/AssetService.java ADDED
@@ -0,0 +1,1955 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.service;
2
+
3
+ import java.time.Instant;
4
+ import java.time.temporal.ChronoUnit;
5
+ import java.util.ArrayList;
6
+ import java.util.Arrays;
7
+ import java.util.HashMap;
8
+ import java.util.List;
9
+ import java.util.Map;
10
+ import java.util.UUID;
11
+ import java.util.stream.Collectors;
12
+
13
+ import org.slf4j.Logger;
14
+ import org.slf4j.LoggerFactory;
15
+ import org.springframework.beans.factory.annotation.Autowired;
16
+ import org.springframework.beans.factory.annotation.Value;
17
+ import org.springframework.data.domain.Page;
18
+ import org.springframework.data.domain.Pageable;
19
+ import org.springframework.data.jpa.domain.Specification;
20
+ import org.springframework.stereotype.Service;
21
+ import org.springframework.transaction.annotation.Transactional;
22
+ import org.springframework.util.StringUtils;
23
+
24
+ import com.dalab.catalog.common.ConflictException;
25
+ import com.dalab.catalog.common.ResourceNotFoundException;
26
+ import com.dalab.catalog.dto.AssetComplianceStatusDTO;
27
+ import com.dalab.catalog.dto.AssetInputDTO;
28
+ import com.dalab.catalog.dto.AssetLabelsPostRequestDTO;
29
+ import com.dalab.catalog.dto.AssetOutputDTO;
30
+ import com.dalab.catalog.dto.AssetPolicyMappingDTO;
31
+ import com.dalab.catalog.dto.AssetSchemaDTO;
32
+ import com.dalab.catalog.dto.AssetUsageAnalyticsDTO;
33
+ import com.dalab.catalog.dto.BusinessMetadataInputDTO;
34
+ import com.dalab.catalog.dto.BusinessMetadataResponseDTO;
35
+ import com.dalab.catalog.dto.CatalogFiltersDTO;
36
+ import com.dalab.catalog.dto.EnhancedLineageDTO;
37
+ import com.dalab.catalog.dto.LabelAssignmentInputDTO;
38
+ import com.dalab.catalog.dto.LabelOutputDTO;
39
+ import com.dalab.catalog.dto.LineageAssetDTO;
40
+ import com.dalab.catalog.dto.LineageResponseDTO;
41
+ import com.dalab.catalog.mapper.AssetMapper;
42
+ import com.dalab.catalog.model.AssetLineage;
43
+ import com.dalab.catalog.repository.AssetLineageRepository;
44
+ import com.dalab.common.model.entity.Asset;
45
+ import com.dalab.common.model.entity.AssetLabelAssignment;
46
+ import com.dalab.common.model.entity.Label;
47
+ import com.dalab.common.repository.IAssetLabelAssignmentRepository;
48
+ import com.dalab.common.repository.IAssetRepository;
49
+ import com.dalab.common.repository.ILabelRepository;
50
+
51
+ import jakarta.persistence.criteria.Predicate;
52
+ import jakarta.persistence.criteria.Root;
53
+ import jakarta.persistence.criteria.Subquery;
54
+
55
+ @Service
56
+ @Transactional
57
+ public class AssetService implements IAssetService {
58
+ private static final Logger log = LoggerFactory.getLogger(AssetService.class);
59
+
60
+ private final IAssetRepository assetRepository;
61
+ private final AssetMapper assetMapper;
62
+ private final ILabelRepository labelRepository;
63
+ private final IAssetLabelAssignmentRepository assetLabelAssignmentRepository;
64
+ private final AssetLineageRepository assetLineageRepository;
65
+ // TODO: Add back Kafka template when protobuf events are set up
66
+ // private final KafkaTemplate<String, AssetChangeEvent> kafkaTemplate;
67
+
68
+ @Value("${dalab.kafka.topics.asset-change-events:asset-change-events}")
69
+ private String assetChangeTopic;
70
+
71
+ @Autowired
72
+ public AssetService(IAssetRepository assetRepository, AssetMapper assetMapper, ILabelRepository labelRepository,
73
+ IAssetLabelAssignmentRepository assetLabelAssignmentRepository,
74
+ AssetLineageRepository assetLineageRepository) {
75
+ // TODO: Add back Kafka template parameter when protobuf events are set up
76
+ // KafkaTemplate<String, AssetChangeEvent> kafkaTemplate) {
77
+ this.assetRepository = assetRepository;
78
+ this.assetMapper = assetMapper;
79
+ this.labelRepository = labelRepository;
80
+ this.assetLabelAssignmentRepository = assetLabelAssignmentRepository;
81
+ this.assetLineageRepository = assetLineageRepository;
82
+ // TODO: Add back when protobuf events are set up
83
+ // this.kafkaTemplate = kafkaTemplate;
84
+ }
85
+
86
+ // TODO: Implement when protobuf events are set up
87
+ /*
88
+ private void sendAssetChangeEvent(Asset asset, ChangeType changeType, String userId, Map<String, String> additionalDetails) {
89
+ // Implementation will be added when protobuf events are ready
90
+ }
91
+ */
92
+
93
+ @Override
94
+ public Page<AssetOutputDTO> getAllAssets(Pageable pageable, String cloudProvider, String assetType,
95
+ String region, List<String> labelNames, String nameContains,
96
+ UUID connectionId) {
97
+ Specification<Asset> spec = (root, query, criteriaBuilder) -> {
98
+ List<Predicate> predicates = new ArrayList<>();
99
+
100
+ if (StringUtils.hasText(cloudProvider)) {
101
+ predicates.add(criteriaBuilder.equal(root.get("cloudProvider"), cloudProvider));
102
+ }
103
+ if (StringUtils.hasText(assetType)) {
104
+ predicates.add(criteriaBuilder.equal(root.get("assetType"), assetType));
105
+ }
106
+ if (StringUtils.hasText(region)) {
107
+ predicates.add(criteriaBuilder.equal(root.get("region"), region));
108
+ }
109
+ if (StringUtils.hasText(nameContains)) {
110
+ predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("assetName")),
111
+ "%" + nameContains.toLowerCase() + "%"));
112
+ }
113
+ if (connectionId != null) {
114
+ predicates.add(criteriaBuilder.equal(root.get("cloudConnectionId"), connectionId));
115
+ }
116
+
117
+ if (labelNames != null && !labelNames.isEmpty()) {
118
+ // 1. Find label IDs for the given label names
119
+ List<UUID> labelIds = labelRepository.findLabelIdsByLabelNames(labelNames);
120
+
121
+ if (labelIds.size() != labelNames.size()) {
122
+ // One or more label names provided do not exist in the database.
123
+ // This means no asset can have all these labels, so return an empty result.
124
+ // Or, throw an exception if this is considered a bad request.
125
+ // For now, effectively results in no assets matching.
126
+ predicates.add(criteriaBuilder.disjunction()); // Always false predicate
127
+ } else if (!labelIds.isEmpty()){
128
+ // Create a subquery to find asset IDs that have all the specified labels.
129
+ Subquery<UUID> subquery = query.subquery(UUID.class);
130
+ Root<AssetLabelAssignment> alaRoot = subquery.from(AssetLabelAssignment.class);
131
+ subquery.select(alaRoot.get("asset").get("assetId"));
132
+ subquery.where(alaRoot.get("label").get("labelId").in(labelIds));
133
+ subquery.groupBy(alaRoot.get("asset").get("assetId"));
134
+ subquery.having(criteriaBuilder.equal(criteriaBuilder.count(alaRoot.get("label").get("labelId")), labelIds.size()));
135
+
136
+ predicates.add(root.get("assetId").in(subquery));
137
+ }
138
+ }
139
+
140
+ return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
141
+ };
142
+
143
+ Page<Asset> assetPage = assetRepository.findAll(spec, pageable);
144
+ return assetPage.map(assetMapper::toAssetOutputDTO);
145
+ }
146
+
147
+ @Override
148
+ public AssetOutputDTO getAssetById(UUID assetId) {
149
+ Asset asset = assetRepository.findById(assetId)
150
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
151
+ return assetMapper.toAssetOutputDTO(asset);
152
+ }
153
+
154
+ @Override
155
+ public AssetOutputDTO createAsset(AssetInputDTO assetInputDTO, UUID creatorUserId) {
156
+ // Check for existing asset with the same nativeAssetId
157
+ assetRepository.findByNativeAssetId(assetInputDTO.getNativeAssetId()).ifPresent(existingAsset -> {
158
+ throw new ConflictException("Asset with nativeAssetId '" + assetInputDTO.getNativeAssetId() + "' already exists.");
159
+ });
160
+
161
+ Asset asset = new Asset();
162
+ // Map fields from DTO
163
+ asset.setAssetName(assetInputDTO.getAssetName());
164
+ asset.setCloudConnectionId(assetInputDTO.getCloudConnectionId());
165
+ asset.setCloudProvider(assetInputDTO.getCloudProvider());
166
+ asset.setAssetType(assetInputDTO.getAssetType());
167
+ asset.setNativeAssetId(assetInputDTO.getNativeAssetId());
168
+ asset.setRegion(assetInputDTO.getRegion());
169
+ asset.setDetailsJson(assetInputDTO.getDetailsJson());
170
+
171
+ // Set system-managed fields
172
+ asset.setCreatedByUserId(creatorUserId);
173
+ asset.setDiscoveredAt(Instant.now()); // Or a more specific discovery timestamp if available from source
174
+ asset.setLastScannedAt(Instant.now()); // Initially same as discovered
175
+ // createdAt and updatedAt are handled by @PrePersist
176
+
177
+ Asset savedAsset = assetRepository.save(asset);
178
+
179
+ // Handle initial labels, if any
180
+ List<String> labelNamesToApply = assetInputDTO.getLabels();
181
+ if (labelNamesToApply != null && !labelNamesToApply.isEmpty()) {
182
+ List<Label> labels = labelRepository.findByLabelNameIn(labelNamesToApply);
183
+ if (labels.size() != labelNamesToApply.size()) {
184
+ List<String> foundLabelNames = labels.stream().map(Label::getLabelName).collect(Collectors.toList());
185
+ List<String> missingLabelNames = labelNamesToApply.stream()
186
+ .filter(name -> !foundLabelNames.contains(name))
187
+ .collect(Collectors.toList());
188
+ if (!missingLabelNames.isEmpty()) {
189
+ log.warn("The following labels were not found and will not be applied: {}", missingLabelNames);
190
+ }
191
+ }
192
+ for (Label label : labels) {
193
+ AssetLabelAssignment assignment = new AssetLabelAssignment();
194
+ assignment.setAsset(savedAsset);
195
+ assignment.setLabel(label);
196
+ assignment.setAssignedByUserId(creatorUserId);
197
+ assignment.setSource("INITIAL_IMPORT");
198
+ assetLabelAssignmentRepository.save(assignment);
199
+ }
200
+ }
201
+
202
+ // TODO: Implement when protobuf events are set up
203
+ /*
204
+ sendAssetChangeEvent(savedAsset, ChangeType.ASSET_CREATED, creatorUserId != null ? creatorUserId.toString() : null, null);
205
+ */
206
+
207
+ return assetMapper.toAssetOutputDTO(savedAsset);
208
+ }
209
+
210
+ @Override
211
+ public AssetOutputDTO updateBusinessMetadata(UUID assetId, Map<String, Object> businessMetadata, UUID updaterUserId) {
212
+ Asset asset = assetRepository.findById(assetId)
213
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
214
+
215
+ asset.setBusinessMetadataJson(businessMetadata);
216
+ Asset updatedAsset = assetRepository.save(asset);
217
+
218
+ Map<String, String> details = Map.of("updated_section", "businessMetadata");
219
+ // TODO: Implement when protobuf events are set up
220
+ /*
221
+ sendAssetChangeEvent(updatedAsset, ChangeType.ASSET_UPDATED, updaterUserId != null ? updaterUserId.toString() : null, details);
222
+ */
223
+
224
+ return assetMapper.toAssetOutputDTO(updatedAsset);
225
+ }
226
+
227
+ @Override
228
+ public BusinessMetadataResponseDTO updateBusinessContext(UUID assetId, BusinessMetadataInputDTO businessMetadata, UUID updaterUserId) {
229
+ // Check if asset exists
230
+ if (!assetRepository.existsById(assetId)) {
231
+ throw new ResourceNotFoundException("Asset", "id", assetId.toString());
232
+ }
233
+
234
+ Asset asset = assetRepository.findById(assetId)
235
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
236
+
237
+ // Update business metadata (placeholder implementation)
238
+ return createBusinessMetadataResponse(asset, businessMetadata);
239
+ }
240
+
241
+ @Override
242
+ public BusinessMetadataResponseDTO getBusinessMetadata(UUID assetId) {
243
+ log.info("Retrieving business metadata for Asset ID: {}", assetId);
244
+
245
+ Asset asset = assetRepository.findById(assetId)
246
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
247
+
248
+ return generateBusinessMetadataResponse(asset, null, null);
249
+ }
250
+
251
+ @Override
252
+ public LineageResponseDTO getAssetLineage(UUID assetId) {
253
+ // First, check if the main asset exists
254
+ if (!assetRepository.existsById(assetId)) {
255
+ throw new ResourceNotFoundException("Asset", "id", assetId.toString());
256
+ }
257
+
258
+ List<AssetLineage> upstreamLineageRecords = assetLineageRepository.findByDownstreamAsset_AssetId(assetId);
259
+ List<AssetLineage> downstreamLineageRecords = assetLineageRepository.findByUpstreamAsset_AssetId(assetId);
260
+
261
+ List<LineageAssetDTO> upstreamAssets = upstreamLineageRecords.stream()
262
+ .map(AssetLineage::getUpstreamAsset)
263
+ .map(asset -> new LineageAssetDTO(
264
+ asset.getAssetId(),
265
+ asset.getAssetName(),
266
+ asset.getAssetType(),
267
+ asset.getCloudProvider()
268
+ // TODO: consider adding relationshipType from AssetLineage record to DTO if needed
269
+ ))
270
+ .collect(Collectors.toList());
271
+
272
+ List<LineageAssetDTO> downstreamAssets = downstreamLineageRecords.stream()
273
+ .map(AssetLineage::getDownstreamAsset)
274
+ .map(asset -> new LineageAssetDTO(
275
+ asset.getAssetId(),
276
+ asset.getAssetName(),
277
+ asset.getAssetType(),
278
+ asset.getCloudProvider()
279
+ // TODO: consider adding relationshipType from AssetLineage record to DTO if needed
280
+ ))
281
+ .collect(Collectors.toList());
282
+
283
+ return new LineageResponseDTO(upstreamAssets, downstreamAssets);
284
+ }
285
+
286
+ @Override
287
+ public List<LabelOutputDTO> getAssetLabels(UUID assetId) {
288
+ if (!assetRepository.existsById(assetId)) {
289
+ throw new ResourceNotFoundException("Asset", "id", assetId.toString());
290
+ }
291
+
292
+ List<AssetLabelAssignment> assignments = assetLabelAssignmentRepository.findByAssetAssetId(assetId);
293
+
294
+ return assignments.stream().map(assignment -> {
295
+ LabelOutputDTO dto = new LabelOutputDTO();
296
+ Label label = assignment.getLabel(); // Get the Label entity
297
+
298
+ // Populate from Label entity
299
+ dto.setLabelId(label.getLabelId());
300
+ dto.setLabelName(label.getLabelName());
301
+ dto.setLabelCategory(label.getLabelCategory());
302
+ dto.setDescription(label.getDescription());
303
+
304
+ // Populate from AssetLabelAssignment entity
305
+ dto.setAssignedByUserId(assignment.getAssignedByUserId());
306
+ dto.setAssignedAt(assignment.getAssignedAt());
307
+ dto.setConfidenceScore(assignment.getConfidenceScore());
308
+ dto.setSource(assignment.getSource());
309
+
310
+ return dto;
311
+ }).collect(Collectors.toList());
312
+ }
313
+
314
+ @Override
315
+ public List<LabelOutputDTO> assignLabelsToAsset(UUID assetId, AssetLabelsPostRequestDTO labelsToAssignRequest, UUID assignerUserId) {
316
+ Asset asset = assetRepository.findById(assetId)
317
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
318
+
319
+ List<AssetLabelAssignment> newAssignments = new ArrayList<>();
320
+ List<String> successfullyAssignedLabelNames = new ArrayList<>();
321
+
322
+ for (LabelAssignmentInputDTO labelInput : labelsToAssignRequest.getLabels()) {
323
+ Label label = labelRepository.findByLabelName(labelInput.getLabelName())
324
+ .orElseGet(() -> {
325
+ // Optionally, create the label if it doesn't exist and if business logic allows
326
+ // For now, we'll skip labels that don't exist or throw an error
327
+ // log.warn("Label '{}' not found. Skipping assignment.", labelInput.getLabelName());
328
+ // return null;
329
+ // Or, more strictly:
330
+ throw new ResourceNotFoundException("Label", "name", labelInput.getLabelName());
331
+ });
332
+
333
+ // if (label == null) continue; // Skip if label not found and we chose to log/warn
334
+
335
+ AssetLabelAssignment assignment = assetLabelAssignmentRepository.findByAssetAndLabel(asset, label)
336
+ .orElse(new AssetLabelAssignment()); // Create new if not exists
337
+
338
+ assignment.setAsset(asset);
339
+ assignment.setLabel(label);
340
+ assignment.setAssignedByUserId(assignerUserId);
341
+ assignment.setSource(StringUtils.hasText(labelInput.getSource()) ? labelInput.getSource() : "USER_ASSIGNED");
342
+ assignment.setConfidenceScore(labelInput.getConfidenceScore());
343
+ // assignedAt is handled by @PrePersist or @PreUpdate
344
+
345
+ newAssignments.add(assetLabelAssignmentRepository.save(assignment));
346
+ successfullyAssignedLabelNames.add(label.getLabelName());
347
+ }
348
+
349
+ if (!successfullyAssignedLabelNames.isEmpty()) {
350
+ Map<String, String> details = Map.of(
351
+ "labels_assigned", String.join(",", successfullyAssignedLabelNames),
352
+ "updated_section", "labels"
353
+ );
354
+ // TODO: Implement when protobuf events are set up
355
+ /*
356
+ sendAssetChangeEvent(asset, ChangeType.ASSET_UPDATED, assignerUserId != null ? assignerUserId.toString() : null, details);
357
+ */
358
+ }
359
+
360
+ // Return the current state of labels for the asset
361
+ return getAssetLabels(assetId);
362
+ }
363
+
364
+ @Override
365
+ public void removeLabelFromAsset(UUID assetId, String labelName, UUID removerUserId) {
366
+ Asset asset = assetRepository.findById(assetId)
367
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
368
+
369
+ Label label = labelRepository.findByLabelName(labelName)
370
+ .orElseThrow(() -> new ResourceNotFoundException("Label", "name", labelName));
371
+
372
+ AssetLabelAssignment assignment = assetLabelAssignmentRepository.findByAssetAndLabel(asset, label)
373
+ .orElseThrow(() -> new ResourceNotFoundException("AssetLabelAssignment", "assetId=" + assetId + ",labelName=" + labelName, ""));
374
+
375
+ assetLabelAssignmentRepository.delete(assignment);
376
+ log.info("Label '{}' removed from asset '{}' by user '{}'", labelName, assetId, removerUserId);
377
+
378
+ Map<String, String> details = Map.of(
379
+ "label_removed", labelName,
380
+ "updated_section", "labels"
381
+ );
382
+ // TODO: Implement when protobuf events are set up
383
+ /*
384
+ sendAssetChangeEvent(asset, ChangeType.ASSET_UPDATED, removerUserId != null ? removerUserId.toString() : null, details);
385
+ */
386
+ }
387
+
388
+ @Override
389
+ public AssetUsageAnalyticsDTO getAssetUsageAnalytics(UUID assetId) {
390
+ log.info("Generating usage analytics for asset: {}", assetId);
391
+
392
+ // First, verify the asset exists
393
+ Asset asset = assetRepository.findById(assetId)
394
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
395
+
396
+ AssetUsageAnalyticsDTO analytics = new AssetUsageAnalyticsDTO();
397
+
398
+ // Generate usage metrics
399
+ AssetUsageAnalyticsDTO.UsageMetricsDTO usageMetrics = createUsageMetrics(asset);
400
+ analytics.setUsageMetrics(usageMetrics);
401
+
402
+ // Generate user analytics
403
+ AssetUsageAnalyticsDTO.UserAnalyticsDTO userAnalytics = createUserAnalytics(asset);
404
+ analytics.setUserAnalytics(userAnalytics);
405
+
406
+ // Generate access patterns
407
+ AssetUsageAnalyticsDTO.AccessPatternsDTO accessPatterns = createAccessPatterns(asset);
408
+ analytics.setAccessPatterns(accessPatterns);
409
+
410
+ // Generate performance metrics
411
+ AssetUsageAnalyticsDTO.PerformanceMetricsDTO performanceMetrics = createPerformanceMetrics(asset);
412
+ analytics.setPerformanceMetrics(performanceMetrics);
413
+
414
+ // Generate popular queries
415
+ List<AssetUsageAnalyticsDTO.PopularQueryDTO> popularQueries = createPopularQueries(asset);
416
+ analytics.setPopularQueries(popularQueries);
417
+
418
+ // Generate recommendations
419
+ List<AssetUsageAnalyticsDTO.RecommendationDTO> recommendations = createRecommendations(asset, usageMetrics, performanceMetrics);
420
+ analytics.setRecommendations(recommendations);
421
+
422
+ log.info("Generated comprehensive usage analytics for asset: {} with {} recommendations", assetId, recommendations.size());
423
+ return analytics;
424
+ }
425
+
426
+ /**
427
+ * Creates usage metrics based on asset characteristics and simulated usage patterns.
428
+ */
429
+ private AssetUsageAnalyticsDTO.UsageMetricsDTO createUsageMetrics(Asset asset) {
430
+ AssetUsageAnalyticsDTO.UsageMetricsDTO metrics = new AssetUsageAnalyticsDTO.UsageMetricsDTO();
431
+
432
+ // Generate realistic usage data based on asset type and age
433
+ long daysSinceDiscovery = asset.getDiscoveredAt() != null ?
434
+ java.time.Duration.between(asset.getDiscoveredAt(), Instant.now()).toDays() : 30;
435
+
436
+ // Base usage on asset type popularity
437
+ int baseAccesses = getBaseAccessesByAssetType(asset.getAssetType());
438
+ long totalAccesses = Math.max(1, baseAccesses * (daysSinceDiscovery / 7)); // Weekly growth
439
+
440
+ metrics.setTotalAccesses(totalAccesses);
441
+ metrics.setUniqueUsers((int) Math.min(47, totalAccesses / 15)); // Realistic user-to-access ratio
442
+ metrics.setAvgAccessesPerDay((double) totalAccesses / Math.max(1, daysSinceDiscovery));
443
+ metrics.setLastAccessed(Instant.now().minusSeconds((long) (Math.random() * 86400))); // Within last day
444
+ metrics.setPeakUsageHour(9 + (int) (Math.random() * 8)); // Business hours 9-17
445
+
446
+ // Determine trend based on recent activity
447
+ String trend = determineUsageTrend(totalAccesses, daysSinceDiscovery);
448
+ metrics.setUsageTrend(trend);
449
+
450
+ return metrics;
451
+ }
452
+
453
+ /**
454
+ * Creates user analytics with department breakdown and top users.
455
+ */
456
+ private AssetUsageAnalyticsDTO.UserAnalyticsDTO createUserAnalytics(Asset asset) {
457
+ AssetUsageAnalyticsDTO.UserAnalyticsDTO userAnalytics = new AssetUsageAnalyticsDTO.UserAnalyticsDTO();
458
+
459
+ // Create top users list
460
+ List<AssetUsageAnalyticsDTO.TopUserDTO> topUsers = createTopUsers(asset);
461
+ userAnalytics.setTopUsers(topUsers);
462
+
463
+ // Create department breakdown based on asset type
464
+ Map<String, Double> departmentBreakdown = createDepartmentBreakdown(asset.getAssetType());
465
+ userAnalytics.setDepartmentBreakdown(departmentBreakdown);
466
+
467
+ return userAnalytics;
468
+ }
469
+
470
+ /**
471
+ * Creates access patterns showing temporal distribution.
472
+ */
473
+ private AssetUsageAnalyticsDTO.AccessPatternsDTO createAccessPatterns(Asset asset) {
474
+ AssetUsageAnalyticsDTO.AccessPatternsDTO patterns = new AssetUsageAnalyticsDTO.AccessPatternsDTO();
475
+
476
+ // Create hourly distribution (24 hours)
477
+ List<Integer> hourlyDistribution = createHourlyDistribution();
478
+ patterns.setHourlyDistribution(hourlyDistribution);
479
+
480
+ // Create weekly distribution (7 days)
481
+ List<Integer> weeklyDistribution = createWeeklyDistribution();
482
+ patterns.setWeeklyDistribution(weeklyDistribution);
483
+
484
+ // Create monthly trend
485
+ List<AssetUsageAnalyticsDTO.MonthlyTrendDTO> monthlyTrend = createMonthlyTrend(asset);
486
+ patterns.setMonthlyTrend(monthlyTrend);
487
+
488
+ return patterns;
489
+ }
490
+
491
+ /**
492
+ * Creates performance metrics based on asset type and characteristics.
493
+ */
494
+ private AssetUsageAnalyticsDTO.PerformanceMetricsDTO createPerformanceMetrics(Asset asset) {
495
+ AssetUsageAnalyticsDTO.PerformanceMetricsDTO performance = new AssetUsageAnalyticsDTO.PerformanceMetricsDTO();
496
+
497
+ // Performance varies by asset type
498
+ double basePerformance = getBasePerformanceByAssetType(asset.getAssetType());
499
+
500
+ performance.setAvgQueryTime(String.format("%.1fs", basePerformance));
501
+ performance.setSlowestQuery(String.format("%.1fs", basePerformance * 20));
502
+ performance.setFastestQuery(String.format("%.1fs", basePerformance * 0.05));
503
+ performance.setTimeoutRate(Math.random() * 0.05); // 0-5% timeout rate
504
+ performance.setErrorRate(Math.random() * 0.01); // 0-1% error rate
505
+
506
+ return performance;
507
+ }
508
+
509
+ /**
510
+ * Creates popular query patterns based on asset type.
511
+ */
512
+ private List<AssetUsageAnalyticsDTO.PopularQueryDTO> createPopularQueries(Asset asset) {
513
+ List<AssetUsageAnalyticsDTO.PopularQueryDTO> queries = new ArrayList<>();
514
+
515
+ // Generate queries based on asset type
516
+ String assetType = asset.getAssetType();
517
+ if (assetType.contains("BigQuery") || assetType.contains("Dataset")) {
518
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
519
+ "SELECT * FROM table WHERE date >= ?", 234, "1.2s"));
520
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
521
+ "SELECT COUNT(*) FROM table GROUP BY category", 189, "2.3s"));
522
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
523
+ "SELECT column1, column2 FROM table WHERE status = ?", 156, "0.8s"));
524
+ } else if (assetType.contains("S3") || assetType.contains("Bucket")) {
525
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
526
+ "LIST objects with prefix 'data/'", 178, "0.3s"));
527
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
528
+ "GET object metadata", 145, "0.1s"));
529
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
530
+ "PUT object with encryption", 89, "1.5s"));
531
+ } else {
532
+ // Generic queries
533
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
534
+ "Access resource metadata", 167, "0.5s"));
535
+ queries.add(new AssetUsageAnalyticsDTO.PopularQueryDTO(
536
+ "List resource contents", 123, "1.1s"));
537
+ }
538
+
539
+ return queries;
540
+ }
541
+
542
+ /**
543
+ * Creates optimization recommendations based on usage patterns and performance.
544
+ */
545
+ private List<AssetUsageAnalyticsDTO.RecommendationDTO> createRecommendations(
546
+ Asset asset,
547
+ AssetUsageAnalyticsDTO.UsageMetricsDTO usageMetrics,
548
+ AssetUsageAnalyticsDTO.PerformanceMetricsDTO performanceMetrics) {
549
+ List<AssetUsageAnalyticsDTO.RecommendationDTO> recommendations = new ArrayList<>();
550
+
551
+ // Performance-based recommendations
552
+ if (performanceMetrics.getTimeoutRate() > 0.02) {
553
+ recommendations.add(new AssetUsageAnalyticsDTO.RecommendationDTO(
554
+ "OPTIMIZATION", "HIGH",
555
+ "High timeout rate detected. Consider optimizing queries or adding indexes.",
556
+ "50% reduction in query timeouts"));
557
+ }
558
+
559
+ // Usage-based recommendations
560
+ if (usageMetrics.getTotalAccesses() > 1000 && usageMetrics.getUniqueUsers() < 10) {
561
+ recommendations.add(new AssetUsageAnalyticsDTO.RecommendationDTO(
562
+ "SECURITY", "MEDIUM",
563
+ "High access volume from few users. Review access permissions.",
564
+ "Improved security posture"));
565
+ }
566
+
567
+ // Asset type specific recommendations
568
+ String assetType = asset.getAssetType();
569
+ if (assetType.contains("BigQuery") || assetType.contains("Dataset")) {
570
+ recommendations.add(new AssetUsageAnalyticsDTO.RecommendationDTO(
571
+ "OPTIMIZATION", "MEDIUM",
572
+ "Consider adding index on frequently queried date column",
573
+ "30% query performance improvement"));
574
+ }
575
+
576
+ if (usageMetrics.getUsageTrend().equals("INCREASING")) {
577
+ recommendations.add(new AssetUsageAnalyticsDTO.RecommendationDTO(
578
+ "MAINTENANCE", "LOW",
579
+ "Usage is increasing. Monitor storage costs and consider archival policies.",
580
+ "10-20% cost reduction through lifecycle management"));
581
+ }
582
+
583
+ // Compliance recommendations
584
+ if (asset.getBusinessMetadataJson() == null || asset.getBusinessMetadataJson().isEmpty()) {
585
+ recommendations.add(new AssetUsageAnalyticsDTO.RecommendationDTO(
586
+ "COMPLIANCE", "HIGH",
587
+ "Missing business metadata. Add data steward and business context information.",
588
+ "Improved data governance and compliance"));
589
+ }
590
+
591
+ return recommendations;
592
+ }
593
+
594
+ // Helper methods for generating realistic data
595
+
596
+ private int getBaseAccessesByAssetType(String assetType) {
597
+ if (assetType == null) return 50;
598
+
599
+ if (assetType.contains("BigQuery") || assetType.contains("Dataset")) {
600
+ return 200; // High usage analytics datasets
601
+ } else if (assetType.contains("S3") || assetType.contains("Bucket")) {
602
+ return 150; // Medium usage file storage
603
+ } else if (assetType.contains("Database") || assetType.contains("Table")) {
604
+ return 300; // High usage transactional data
605
+ } else {
606
+ return 100; // Default moderate usage
607
+ }
608
+ }
609
+
610
+ private String determineUsageTrend(long totalAccesses, long daysSinceDiscovery) {
611
+ // Simple trend calculation based on access patterns
612
+ double accessesPerDay = (double) totalAccesses / Math.max(1, daysSinceDiscovery);
613
+
614
+ if (accessesPerDay > 50) {
615
+ return "INCREASING";
616
+ } else if (accessesPerDay < 10) {
617
+ return "DECREASING";
618
+ } else {
619
+ return "STABLE";
620
+ }
621
+ }
622
+
623
+ private List<AssetUsageAnalyticsDTO.TopUserDTO> createTopUsers(Asset asset) {
624
+ List<AssetUsageAnalyticsDTO.TopUserDTO> topUsers = new ArrayList<>();
625
+
626
+ // Generate realistic top users
627
+ topUsers.add(new AssetUsageAnalyticsDTO.TopUserDTO(
628
+ UUID.randomUUID(), "john.doe@company.com", 234,
629
+ Instant.now().minus(java.time.Duration.ofHours(2)), "REGULAR"));
630
+ topUsers.add(new AssetUsageAnalyticsDTO.TopUserDTO(
631
+ UUID.randomUUID(), "sarah.smith@company.com", 189,
632
+ Instant.now().minus(java.time.Duration.ofHours(5)), "HEAVY"));
633
+ topUsers.add(new AssetUsageAnalyticsDTO.TopUserDTO(
634
+ UUID.randomUUID(), "mike.johnson@company.com", 156,
635
+ Instant.now().minus(java.time.Duration.ofHours(8)), "OCCASIONAL"));
636
+
637
+ return topUsers;
638
+ }
639
+
640
+ private Map<String, Double> createDepartmentBreakdown(String assetType) {
641
+ Map<String, Double> breakdown = new HashMap<>();
642
+
643
+ if (assetType != null && (assetType.contains("Analytics") || assetType.contains("BigQuery"))) {
644
+ // Analytics-heavy departments for data assets
645
+ breakdown.put("Analytics", 45.2);
646
+ breakdown.put("Data Engineering", 32.1);
647
+ breakdown.put("Finance", 22.7);
648
+ } else if (assetType != null && assetType.contains("S3")) {
649
+ // More distributed access for file storage
650
+ breakdown.put("Data Engineering", 38.5);
651
+ breakdown.put("Analytics", 28.3);
652
+ breakdown.put("Operations", 20.1);
653
+ breakdown.put("Development", 13.1);
654
+ } else {
655
+ // General purpose access pattern
656
+ breakdown.put("Analytics", 35.0);
657
+ breakdown.put("Data Engineering", 30.0);
658
+ breakdown.put("Finance", 20.0);
659
+ breakdown.put("Operations", 15.0);
660
+ }
661
+
662
+ return breakdown;
663
+ }
664
+
665
+ private List<Integer> createHourlyDistribution() {
666
+ // Business hours pattern with peak around 10-14
667
+ List<Integer> hourly = new ArrayList<>();
668
+ int[] pattern = {0, 2, 1, 0, 0, 5, 12, 25, 89, 156, 234, 189, 167, 145, 123, 98, 67, 45, 23, 12, 5, 2, 1, 0};
669
+ for (int access : pattern) {
670
+ hourly.add(access);
671
+ }
672
+ return hourly;
673
+ }
674
+
675
+ private List<Integer> createWeeklyDistribution() {
676
+ // Weekday heavy pattern
677
+ List<Integer> weekly = new ArrayList<>();
678
+ int[] pattern = {156, 234, 189, 167, 145, 98, 67}; // Mon-Sun
679
+ for (int access : pattern) {
680
+ weekly.add(access);
681
+ }
682
+ return weekly;
683
+ }
684
+
685
+ private List<AssetUsageAnalyticsDTO.MonthlyTrendDTO> createMonthlyTrend(Asset asset) {
686
+ List<AssetUsageAnalyticsDTO.MonthlyTrendDTO> trend = new ArrayList<>();
687
+
688
+ // Generate last 6 months of data
689
+ java.time.LocalDate currentDate = java.time.LocalDate.now();
690
+ for (int i = 5; i >= 0; i--) {
691
+ java.time.LocalDate monthDate = currentDate.minusMonths(i);
692
+ String monthStr = monthDate.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM"));
693
+
694
+ // Simulate growth or decline
695
+ int baseAccesses = 10000 + (int) (Math.random() * 5000);
696
+ int uniqueUsers = 35 + (int) (Math.random() * 15);
697
+
698
+ trend.add(new AssetUsageAnalyticsDTO.MonthlyTrendDTO(monthStr, baseAccesses, uniqueUsers));
699
+ }
700
+
701
+ return trend;
702
+ }
703
+
704
+ private double getBasePerformanceByAssetType(String assetType) {
705
+ if (assetType == null) return 2.0;
706
+
707
+ if (assetType.contains("BigQuery") || assetType.contains("Dataset")) {
708
+ return 2.3; // Slower for complex analytics
709
+ } else if (assetType.contains("S3") || assetType.contains("Bucket")) {
710
+ return 0.5; // Fast for file operations
711
+ } else if (assetType.contains("Database")) {
712
+ return 1.2; // Medium for database operations
713
+ } else {
714
+ return 1.5; // Default performance
715
+ }
716
+ }
717
+
718
+ /**
719
+ * Convert BusinessMetadataInputDTO to storage format (Map<String, Object>)
720
+ */
721
+ private Map<String, Object> convertToStorageFormat(BusinessMetadataInputDTO input) {
722
+ Map<String, Object> storage = new HashMap<>();
723
+
724
+ if (input.getBusinessMetadata() != null) {
725
+ BusinessMetadataInputDTO.BusinessMetadataDTO bm = input.getBusinessMetadata();
726
+
727
+ storage.put("businessName", bm.getBusinessName());
728
+ storage.put("businessDescription", bm.getBusinessDescription());
729
+ storage.put("businessCriticality", bm.getBusinessCriticality());
730
+ storage.put("businessDomain", bm.getBusinessDomain());
731
+ storage.put("useCases", bm.getUseCases());
732
+ storage.put("dataQualityScore", bm.getDataQualityScore());
733
+ storage.put("updateFrequency", bm.getUpdateFrequency());
734
+
735
+ // Business Owner
736
+ if (bm.getBusinessOwner() != null) {
737
+ Map<String, Object> owner = new HashMap<>();
738
+ owner.put("userId", bm.getBusinessOwner().getUserId());
739
+ owner.put("name", bm.getBusinessOwner().getName());
740
+ owner.put("email", bm.getBusinessOwner().getEmail());
741
+ owner.put("department", bm.getBusinessOwner().getDepartment());
742
+ owner.put("role", bm.getBusinessOwner().getRole());
743
+ owner.put("phone", bm.getBusinessOwner().getPhone());
744
+ storage.put("businessOwner", owner);
745
+ }
746
+
747
+ // Data Steward
748
+ if (bm.getDataSteward() != null) {
749
+ Map<String, Object> steward = new HashMap<>();
750
+ steward.put("userId", bm.getDataSteward().getUserId());
751
+ steward.put("name", bm.getDataSteward().getName());
752
+ steward.put("email", bm.getDataSteward().getEmail());
753
+ steward.put("department", bm.getDataSteward().getDepartment());
754
+ steward.put("role", bm.getDataSteward().getRole());
755
+ steward.put("phone", bm.getDataSteward().getPhone());
756
+ storage.put("dataSteward", steward);
757
+ }
758
+
759
+ // Retention Requirements
760
+ if (bm.getRetentionRequirements() != null) {
761
+ Map<String, Object> retention = new HashMap<>();
762
+ retention.put("legalRetention", bm.getRetentionRequirements().getLegalRetention());
763
+ retention.put("businessRetention", bm.getRetentionRequirements().getBusinessRetention());
764
+ retention.put("archiveAfter", bm.getRetentionRequirements().getArchiveAfter());
765
+ retention.put("deleteAfter", bm.getRetentionRequirements().getDeleteAfter());
766
+ retention.put("retentionReasons", bm.getRetentionRequirements().getRetentionReasons());
767
+ storage.put("retentionRequirements", retention);
768
+ }
769
+ }
770
+
771
+ if (input.getComplianceClassification() != null) {
772
+ BusinessMetadataInputDTO.ComplianceClassificationDTO cc = input.getComplianceClassification();
773
+
774
+ Map<String, Object> compliance = new HashMap<>();
775
+ compliance.put("dataClassification", cc.getDataClassification());
776
+ compliance.put("containsPii", cc.getContainsPii());
777
+ compliance.put("containsPhi", cc.getContainsPhi());
778
+ compliance.put("containsFinancial", cc.getContainsFinancial());
779
+ compliance.put("gdprApplicable", cc.getGdprApplicable());
780
+ compliance.put("soxRelevant", cc.getSoxRelevant());
781
+ compliance.put("hipaaRelevant", cc.getHipaaRelevant());
782
+ compliance.put("pciRelevant", cc.getPciRelevant());
783
+ compliance.put("regulatoryFrameworks", cc.getRegulatoryFrameworks());
784
+ compliance.put("dataSubjectCategories", cc.getDataSubjectCategories());
785
+ compliance.put("privacyImpactLevel", cc.getPrivacyImpactLevel());
786
+ storage.put("complianceClassification", compliance);
787
+ }
788
+
789
+ return storage;
790
+ }
791
+
792
+ /**
793
+ * Generate comprehensive business metadata response with validation and recommendations
794
+ */
795
+ private BusinessMetadataResponseDTO generateBusinessMetadataResponse(Asset asset, BusinessMetadataInputDTO input, UUID updaterUserId) {
796
+ BusinessMetadataResponseDTO response = new BusinessMetadataResponseDTO();
797
+
798
+ // Convert storage format back to DTOs
799
+ if (asset.getBusinessMetadataJson() != null) {
800
+ response.setBusinessMetadata(convertStorageToBusinessMetadataDTO(asset.getBusinessMetadataJson()));
801
+ response.setComplianceClassification(convertStorageToComplianceDTO(asset.getBusinessMetadataJson()));
802
+ }
803
+
804
+ // Generate audit information
805
+ response.setAuditInfo(generateAuditInfo(asset, updaterUserId));
806
+
807
+ // Generate validation status
808
+ response.setValidationStatus(generateValidationStatus(asset.getBusinessMetadataJson()));
809
+
810
+ // Generate recommendations
811
+ response.setRecommendations(generateMetadataRecommendations(asset));
812
+
813
+ return response;
814
+ }
815
+
816
+ /**
817
+ * Convert storage format to BusinessMetadataDTO
818
+ */
819
+ @SuppressWarnings("unchecked")
820
+ private BusinessMetadataResponseDTO.BusinessMetadataDTO convertStorageToBusinessMetadataDTO(Map<String, Object> storage) {
821
+ BusinessMetadataResponseDTO.BusinessMetadataDTO dto = new BusinessMetadataResponseDTO.BusinessMetadataDTO();
822
+
823
+ dto.setBusinessName((String) storage.get("businessName"));
824
+ dto.setBusinessDescription((String) storage.get("businessDescription"));
825
+ dto.setBusinessCriticality((String) storage.get("businessCriticality"));
826
+ dto.setBusinessDomain((String) storage.get("businessDomain"));
827
+ dto.setUseCases((List<String>) storage.get("useCases"));
828
+
829
+ Object qualityScore = storage.get("dataQualityScore");
830
+ if (qualityScore instanceof Number) {
831
+ dto.setDataQualityScore(((Number) qualityScore).doubleValue());
832
+ }
833
+
834
+ dto.setUpdateFrequency((String) storage.get("updateFrequency"));
835
+
836
+ // Convert business owner
837
+ Map<String, Object> ownerMap = (Map<String, Object>) storage.get("businessOwner");
838
+ if (ownerMap != null) {
839
+ BusinessMetadataInputDTO.BusinessContactDTO owner = new BusinessMetadataInputDTO.BusinessContactDTO();
840
+ owner.setUserId(UUID.fromString((String) ownerMap.get("userId")));
841
+ owner.setName((String) ownerMap.get("name"));
842
+ owner.setEmail((String) ownerMap.get("email"));
843
+ owner.setDepartment((String) ownerMap.get("department"));
844
+ owner.setRole((String) ownerMap.get("role"));
845
+ owner.setPhone((String) ownerMap.get("phone"));
846
+ dto.setBusinessOwner(owner);
847
+ }
848
+
849
+ // Convert data steward
850
+ Map<String, Object> stewardMap = (Map<String, Object>) storage.get("dataSteward");
851
+ if (stewardMap != null) {
852
+ BusinessMetadataInputDTO.BusinessContactDTO steward = new BusinessMetadataInputDTO.BusinessContactDTO();
853
+ steward.setUserId(UUID.fromString((String) stewardMap.get("userId")));
854
+ steward.setName((String) stewardMap.get("name"));
855
+ steward.setEmail((String) stewardMap.get("email"));
856
+ steward.setDepartment((String) stewardMap.get("department"));
857
+ steward.setRole((String) stewardMap.get("role"));
858
+ steward.setPhone((String) stewardMap.get("phone"));
859
+ dto.setDataSteward(steward);
860
+ }
861
+
862
+ // Convert retention requirements
863
+ Map<String, Object> retentionMap = (Map<String, Object>) storage.get("retentionRequirements");
864
+ if (retentionMap != null) {
865
+ BusinessMetadataInputDTO.RetentionRequirementsDTO retention = new BusinessMetadataInputDTO.RetentionRequirementsDTO();
866
+ retention.setLegalRetention((String) retentionMap.get("legalRetention"));
867
+ retention.setBusinessRetention((String) retentionMap.get("businessRetention"));
868
+ retention.setArchiveAfter((String) retentionMap.get("archiveAfter"));
869
+ retention.setDeleteAfter((String) retentionMap.get("deleteAfter"));
870
+ retention.setRetentionReasons((List<String>) retentionMap.get("retentionReasons"));
871
+ dto.setRetentionRequirements(retention);
872
+ }
873
+
874
+ return dto;
875
+ }
876
+
877
+ /**
878
+ * Convert storage format to ComplianceClassificationDTO
879
+ */
880
+ @SuppressWarnings("unchecked")
881
+ private BusinessMetadataResponseDTO.ComplianceClassificationDTO convertStorageToComplianceDTO(Map<String, Object> storage) {
882
+ Map<String, Object> complianceMap = (Map<String, Object>) storage.get("complianceClassification");
883
+ if (complianceMap == null) {
884
+ return null;
885
+ }
886
+
887
+ BusinessMetadataResponseDTO.ComplianceClassificationDTO dto = new BusinessMetadataResponseDTO.ComplianceClassificationDTO();
888
+
889
+ dto.setDataClassification((String) complianceMap.get("dataClassification"));
890
+ dto.setContainsPii((Boolean) complianceMap.get("containsPii"));
891
+ dto.setContainsPhi((Boolean) complianceMap.get("containsPhi"));
892
+ dto.setContainsFinancial((Boolean) complianceMap.get("containsFinancial"));
893
+ dto.setGdprApplicable((Boolean) complianceMap.get("gdprApplicable"));
894
+ dto.setSoxRelevant((Boolean) complianceMap.get("soxRelevant"));
895
+ dto.setHipaaRelevant((Boolean) complianceMap.get("hipaaRelevant"));
896
+ dto.setPciRelevant((Boolean) complianceMap.get("pciRelevant"));
897
+ dto.setRegulatoryFrameworks((List<String>) complianceMap.get("regulatoryFrameworks"));
898
+ dto.setDataSubjectCategories((List<String>) complianceMap.get("dataSubjectCategories"));
899
+ dto.setPrivacyImpactLevel((String) complianceMap.get("privacyImpactLevel"));
900
+
901
+ return dto;
902
+ }
903
+
904
+ /**
905
+ * Generate audit information
906
+ */
907
+ private BusinessMetadataResponseDTO.MetadataAuditDTO generateAuditInfo(Asset asset, UUID updaterUserId) {
908
+ BusinessMetadataResponseDTO.MetadataAuditDTO audit = new BusinessMetadataResponseDTO.MetadataAuditDTO();
909
+
910
+ Map<String, Object> metadata = asset.getBusinessMetadataJson();
911
+ if (metadata != null) {
912
+ // Last updated info
913
+ String lastUpdatedByStr = (String) metadata.get("lastUpdatedBy");
914
+ if (lastUpdatedByStr != null) {
915
+ audit.setLastUpdatedBy(UUID.fromString(lastUpdatedByStr));
916
+ audit.setLastUpdatedByName("System User"); // TODO: Lookup user name
917
+ }
918
+
919
+ String lastUpdatedAtStr = (String) metadata.get("lastUpdatedAt");
920
+ if (lastUpdatedAtStr != null) {
921
+ audit.setLastUpdatedAt(Instant.parse(lastUpdatedAtStr));
922
+ }
923
+
924
+ audit.setUpdateReason((String) metadata.get("updateReason"));
925
+
926
+ Object versionObj = metadata.get("versionNumber");
927
+ if (versionObj instanceof Number) {
928
+ audit.setVersionNumber(((Number) versionObj).intValue());
929
+ }
930
+ }
931
+
932
+ // Created info from asset
933
+ audit.setCreatedBy(asset.getCreatedByUserId());
934
+ audit.setCreatedByName("System User"); // TODO: Lookup user name
935
+ audit.setCreatedAt(asset.getCreatedTs());
936
+
937
+ // Generate change history
938
+ audit.setChangeHistory(generateChangeHistory(asset));
939
+
940
+ return audit;
941
+ }
942
+
943
+ /**
944
+ * Generate validation status for metadata completeness
945
+ */
946
+ private BusinessMetadataResponseDTO.ValidationStatusDTO generateValidationStatus(Map<String, Object> metadata) {
947
+ BusinessMetadataResponseDTO.ValidationStatusDTO validation = new BusinessMetadataResponseDTO.ValidationStatusDTO();
948
+
949
+ List<String> missingFields = new ArrayList<>();
950
+ List<String> warnings = new ArrayList<>();
951
+ List<String> errors = new ArrayList<>();
952
+
953
+ int totalFields = 15; // Total important fields to check
954
+ int presentFields = 0;
955
+
956
+ if (metadata == null || metadata.isEmpty()) {
957
+ validation.setIsComplete(false);
958
+ validation.setCompletenessScore(0.0);
959
+ validation.setOverallStatus("INCOMPLETE");
960
+ missingFields.add("All business metadata fields are missing");
961
+ validation.setMissingFields(missingFields);
962
+ validation.setValidationWarnings(warnings);
963
+ validation.setValidationErrors(errors);
964
+ return validation;
965
+ }
966
+
967
+ // Check core business metadata
968
+ if (metadata.get("businessName") != null) presentFields++;
969
+ else missingFields.add("businessName");
970
+
971
+ if (metadata.get("businessDescription") != null) presentFields++;
972
+ else missingFields.add("businessDescription");
973
+
974
+ if (metadata.get("businessCriticality") != null) presentFields++;
975
+ else missingFields.add("businessCriticality");
976
+
977
+ if (metadata.get("businessDomain") != null) presentFields++;
978
+ else missingFields.add("businessDomain");
979
+
980
+ // Check steward relationships
981
+ Map<String, Object> owner = (Map<String, Object>) metadata.get("businessOwner");
982
+ if (owner != null && owner.get("name") != null && owner.get("email") != null) {
983
+ presentFields++;
984
+ } else {
985
+ missingFields.add("businessOwner");
986
+ }
987
+
988
+ Map<String, Object> steward = (Map<String, Object>) metadata.get("dataSteward");
989
+ if (steward != null && steward.get("name") != null && steward.get("email") != null) {
990
+ presentFields++;
991
+ } else {
992
+ missingFields.add("dataSteward");
993
+ }
994
+
995
+ // Check compliance classification
996
+ Map<String, Object> compliance = (Map<String, Object>) metadata.get("complianceClassification");
997
+ if (compliance != null && compliance.get("dataClassification") != null) {
998
+ presentFields++;
999
+ } else {
1000
+ missingFields.add("complianceClassification");
1001
+ }
1002
+
1003
+ // Calculate completeness
1004
+ double completenessScore = (double) presentFields / totalFields;
1005
+ validation.setCompletenessScore(completenessScore);
1006
+ validation.setIsComplete(completenessScore >= 0.8);
1007
+
1008
+ // Determine overall status
1009
+ if (completenessScore >= 0.9) {
1010
+ validation.setOverallStatus("COMPLETE");
1011
+ } else if (completenessScore >= 0.6) {
1012
+ validation.setOverallStatus("PARTIAL");
1013
+ } else {
1014
+ validation.setOverallStatus("INCOMPLETE");
1015
+ }
1016
+
1017
+ // Add warnings for low-quality data
1018
+ Object qualityScore = metadata.get("dataQualityScore");
1019
+ if (qualityScore instanceof Number && ((Number) qualityScore).doubleValue() < 5.0) {
1020
+ warnings.add("Data quality score is below recommended threshold (5.0)");
1021
+ }
1022
+
1023
+ validation.setMissingFields(missingFields);
1024
+ validation.setValidationWarnings(warnings);
1025
+ validation.setValidationErrors(errors);
1026
+
1027
+ return validation;
1028
+ }
1029
+
1030
+ /**
1031
+ * Generate metadata recommendations
1032
+ */
1033
+ private List<BusinessMetadataResponseDTO.RecommendationDTO> generateMetadataRecommendations(Asset asset) {
1034
+ List<BusinessMetadataResponseDTO.RecommendationDTO> recommendations = new ArrayList<>();
1035
+
1036
+ Map<String, Object> metadata = asset.getBusinessMetadataJson();
1037
+
1038
+ if (metadata == null || metadata.isEmpty()) {
1039
+ recommendations.add(new BusinessMetadataResponseDTO.RecommendationDTO(
1040
+ "COMPLETENESS", "HIGH", "Missing Business Metadata",
1041
+ "This asset has no business metadata defined. Adding business context will improve governance and discoverability.",
1042
+ "Add business owner, data steward, business description, and compliance classification",
1043
+ "Improved data governance, compliance reporting, and asset discoverability"
1044
+ ));
1045
+ return recommendations;
1046
+ }
1047
+
1048
+ // Check for missing steward
1049
+ if (metadata.get("dataSteward") == null) {
1050
+ recommendations.add(new BusinessMetadataResponseDTO.RecommendationDTO(
1051
+ "GOVERNANCE", "HIGH", "Missing Data Steward",
1052
+ "No data steward assigned to this asset. A data steward is responsible for data quality and governance.",
1053
+ "Assign a qualified data steward with appropriate technical and business knowledge",
1054
+ "Better data quality management and faster issue resolution"
1055
+ ));
1056
+ }
1057
+
1058
+ // Check for missing compliance classification
1059
+ if (metadata.get("complianceClassification") == null) {
1060
+ recommendations.add(new BusinessMetadataResponseDTO.RecommendationDTO(
1061
+ "COMPLIANCE", "HIGH", "Missing Compliance Classification",
1062
+ "Asset lacks compliance classification which is required for regulatory reporting and risk management.",
1063
+ "Define data classification, PII/PHI status, and applicable regulatory frameworks",
1064
+ "Improved compliance posture and reduced regulatory risk"
1065
+ ));
1066
+ }
1067
+
1068
+ // Check for low data quality score
1069
+ Object qualityScore = metadata.get("dataQualityScore");
1070
+ if (qualityScore instanceof Number && ((Number) qualityScore).doubleValue() < 6.0) {
1071
+ recommendations.add(new BusinessMetadataResponseDTO.RecommendationDTO(
1072
+ "QUALITY", "MEDIUM", "Low Data Quality Score",
1073
+ "Data quality score is below recommended threshold. Consider implementing data quality improvements.",
1074
+ "Review data quality issues, implement validation rules, and establish monitoring",
1075
+ "Increased trust in data and better decision-making outcomes"
1076
+ ));
1077
+ }
1078
+
1079
+ // Check for missing business description
1080
+ if (metadata.get("businessDescription") == null || ((String) metadata.get("businessDescription")).trim().isEmpty()) {
1081
+ recommendations.add(new BusinessMetadataResponseDTO.RecommendationDTO(
1082
+ "COMPLETENESS", "MEDIUM", "Missing Business Description",
1083
+ "Asset lacks a business description which helps users understand its purpose and context.",
1084
+ "Add a comprehensive business description explaining the asset's purpose and use cases",
1085
+ "Improved asset discoverability and user understanding"
1086
+ ));
1087
+ }
1088
+
1089
+ return recommendations;
1090
+ }
1091
+
1092
+ /**
1093
+ * Get current metadata version number
1094
+ */
1095
+ private Integer getCurrentMetadataVersion(Map<String, Object> metadata) {
1096
+ if (metadata == null) {
1097
+ return 0;
1098
+ }
1099
+
1100
+ Object version = metadata.get("versionNumber");
1101
+ if (version instanceof Number) {
1102
+ return ((Number) version).intValue();
1103
+ }
1104
+
1105
+ return 0;
1106
+ }
1107
+
1108
+ /**
1109
+ * Generate change history summary
1110
+ */
1111
+ private List<String> generateChangeHistory(Asset asset) {
1112
+ List<String> history = new ArrayList<>();
1113
+
1114
+ // For now, generate basic history based on asset timestamps
1115
+ if (asset.getCreatedTs() != null) {
1116
+ history.add("Asset created on " + asset.getCreatedTs());
1117
+ }
1118
+
1119
+ if (asset.getLastModifiedTs() != null && !asset.getLastModifiedTs().equals(asset.getCreatedTs())) {
1120
+ history.add("Last modified on " + asset.getLastModifiedTs());
1121
+ }
1122
+
1123
+ // TODO: Implement proper change tracking for business metadata
1124
+ Map<String, Object> metadata = asset.getBusinessMetadataJson();
1125
+ if (metadata != null && metadata.get("lastUpdatedAt") != null) {
1126
+ history.add("Business metadata last updated on " + metadata.get("lastUpdatedAt"));
1127
+ }
1128
+
1129
+ return history;
1130
+ }
1131
+
1132
+ @Override
1133
+ public AssetSchemaDTO getAssetSchema(UUID assetId) {
1134
+ // Check if asset exists
1135
+ if (!assetRepository.existsById(assetId)) {
1136
+ throw new ResourceNotFoundException("Asset", "id", assetId.toString());
1137
+ }
1138
+
1139
+ Asset asset = assetRepository.findById(assetId)
1140
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
1141
+
1142
+ // Generate comprehensive schema information
1143
+ return createAssetSchema(asset);
1144
+ }
1145
+
1146
+ /**
1147
+ * Creates comprehensive asset schema information with realistic mock data.
1148
+ * Demonstrates all aspects of the AssetSchemaDTO specification for Priority 2 implementation.
1149
+ */
1150
+ private AssetSchemaDTO createAssetSchema(Asset asset) {
1151
+ AssetSchemaDTO schema = new AssetSchemaDTO();
1152
+ schema.setAssetId(asset.getAssetId());
1153
+ schema.setAssetName(asset.getAssetName());
1154
+ schema.setSchemaVersion("2.1.0");
1155
+ schema.setLastUpdated(Instant.now().minus(java.time.Duration.ofDays(2)));
1156
+ schema.setLastAnalyzed(Instant.now().minus(java.time.Duration.ofHours(6)));
1157
+ schema.setSource("AUTOMATED_DISCOVERY");
1158
+
1159
+ // Schema Type Information
1160
+ AssetSchemaDTO.SchemaTypeDTO schemaType = new AssetSchemaDTO.SchemaTypeDTO();
1161
+ schemaType.setType("TABLE");
1162
+ schemaType.setFormat("PARQUET");
1163
+ schemaType.setEncoding("UTF-8");
1164
+ Map<String, Object> formatProps = new HashMap<>();
1165
+ formatProps.put("compression", "SNAPPY");
1166
+ formatProps.put("blockSize", "134217728");
1167
+ formatProps.put("pageSize", "1048576");
1168
+ schemaType.setFormatSpecificProperties(formatProps);
1169
+ schema.setSchemaType(schemaType);
1170
+
1171
+ // Schema Fields with comprehensive metadata
1172
+ List<AssetSchemaDTO.SchemaFieldDTO> fields = createSchemaFields(asset);
1173
+ schema.setFields(fields);
1174
+
1175
+ // Schema Constraints
1176
+ List<AssetSchemaDTO.SchemaConstraintDTO> constraints = createSchemaConstraints();
1177
+ schema.setConstraints(constraints);
1178
+
1179
+ // Schema Indexes
1180
+ List<AssetSchemaDTO.SchemaIndexDTO> indexes = createSchemaIndexes();
1181
+ schema.setIndexes(indexes);
1182
+
1183
+ // Schema Statistics
1184
+ AssetSchemaDTO.SchemaStatisticsDTO statistics = createSchemaStatistics();
1185
+ schema.setStatistics(statistics);
1186
+
1187
+ // Schema Lineage
1188
+ AssetSchemaDTO.SchemaLineageDTO lineage = createSchemaLineage(asset);
1189
+ schema.setLineage(lineage);
1190
+
1191
+ // Schema Evolution
1192
+ AssetSchemaDTO.SchemaEvolutionDTO evolution = createSchemaEvolution();
1193
+ schema.setEvolution(evolution);
1194
+
1195
+ // Additional metadata
1196
+ Map<String, Object> metadata = new HashMap<>();
1197
+ metadata.put("tableType", "EXTERNAL");
1198
+ metadata.put("location", "s3://data-lake/sales/customer_orders/");
1199
+ metadata.put("partitioned", true);
1200
+ metadata.put("partitionKeys", Arrays.asList("year", "month", "day"));
1201
+ metadata.put("fileFormat", "PARQUET");
1202
+ metadata.put("serde", "org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe");
1203
+ schema.setMetadata(metadata);
1204
+
1205
+ return schema;
1206
+ }
1207
+
1208
+ /**
1209
+ * Creates detailed schema fields with comprehensive metadata and statistics.
1210
+ */
1211
+ private List<AssetSchemaDTO.SchemaFieldDTO> createSchemaFields(Asset asset) {
1212
+ List<AssetSchemaDTO.SchemaFieldDTO> fields = new ArrayList<>();
1213
+
1214
+ // Primary key field
1215
+ AssetSchemaDTO.SchemaFieldDTO idField = new AssetSchemaDTO.SchemaFieldDTO();
1216
+ idField.setFieldName("customer_id");
1217
+ idField.setDataType("BIGINT");
1218
+ idField.setLogicalType("IDENTIFIER");
1219
+ idField.setNullable(false);
1220
+ idField.setPrimaryKey(true);
1221
+ idField.setForeignKey(false);
1222
+ idField.setDescription("Unique identifier for customer");
1223
+ idField.setTags(Arrays.asList("PII", "PRIMARY_KEY", "IDENTIFIER"));
1224
+ idField.setOrdinalPosition(1);
1225
+ idField.setClassification("PII");
1226
+
1227
+ // Field statistics
1228
+ AssetSchemaDTO.FieldStatisticsDTO idStats = new AssetSchemaDTO.FieldStatisticsDTO();
1229
+ idStats.setDistinctCount(125000L);
1230
+ idStats.setNullCount(0L);
1231
+ idStats.setFillRate(100.0);
1232
+ idStats.setMinValue("1000001");
1233
+ idStats.setMaxValue("1125000");
1234
+ idStats.setLastAnalyzed(Instant.now().minus(java.time.Duration.ofHours(6)));
1235
+ idField.setStatistics(idStats);
1236
+
1237
+ fields.add(idField);
1238
+
1239
+ // Email field with constraints
1240
+ AssetSchemaDTO.SchemaFieldDTO emailField = new AssetSchemaDTO.SchemaFieldDTO();
1241
+ emailField.setFieldName("email");
1242
+ emailField.setDataType("VARCHAR");
1243
+ emailField.setLogicalType("EMAIL");
1244
+ emailField.setNullable(false);
1245
+ emailField.setPrimaryKey(false);
1246
+ emailField.setForeignKey(false);
1247
+ emailField.setDescription("Customer email address");
1248
+ emailField.setTags(Arrays.asList("PII", "CONTACT", "UNIQUE"));
1249
+ emailField.setOrdinalPosition(2);
1250
+ emailField.setClassification("PII");
1251
+
1252
+ // Field constraints
1253
+ AssetSchemaDTO.FieldConstraintsDTO emailConstraints = new AssetSchemaDTO.FieldConstraintsDTO();
1254
+ emailConstraints.setPattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
1255
+ emailConstraints.setMaxLength(255);
1256
+ emailConstraints.setFormat("EMAIL");
1257
+ emailField.setConstraints(emailConstraints);
1258
+
1259
+ // Email statistics
1260
+ AssetSchemaDTO.FieldStatisticsDTO emailStats = new AssetSchemaDTO.FieldStatisticsDTO();
1261
+ emailStats.setDistinctCount(124500L);
1262
+ emailStats.setNullCount(0L);
1263
+ emailStats.setFillRate(100.0);
1264
+ emailStats.setAvgLength(28.5);
1265
+ emailStats.setMaxLength(64.0);
1266
+ emailStats.setTopValues(Arrays.asList("gmail.com", "yahoo.com", "outlook.com", "company.com", "hotmail.com"));
1267
+ emailStats.setLastAnalyzed(Instant.now().minus(java.time.Duration.ofHours(6)));
1268
+ emailField.setStatistics(emailStats);
1269
+
1270
+ fields.add(emailField);
1271
+
1272
+ // Date field
1273
+ AssetSchemaDTO.SchemaFieldDTO createdField = new AssetSchemaDTO.SchemaFieldDTO();
1274
+ createdField.setFieldName("created_date");
1275
+ createdField.setDataType("DATE");
1276
+ createdField.setLogicalType("TIMESTAMP");
1277
+ createdField.setNullable(false);
1278
+ createdField.setDefaultValue("CURRENT_DATE");
1279
+ createdField.setDescription("Date when customer record was created");
1280
+ createdField.setTags(Arrays.asList("AUDIT", "TEMPORAL"));
1281
+ createdField.setOrdinalPosition(3);
1282
+ createdField.setClassification("PUBLIC");
1283
+
1284
+ // Date statistics
1285
+ AssetSchemaDTO.FieldStatisticsDTO dateStats = new AssetSchemaDTO.FieldStatisticsDTO();
1286
+ dateStats.setDistinctCount(365L);
1287
+ dateStats.setNullCount(0L);
1288
+ dateStats.setFillRate(100.0);
1289
+ dateStats.setMinValue("2023-01-01");
1290
+ dateStats.setMaxValue("2024-12-31");
1291
+ dateStats.setLastAnalyzed(Instant.now().minus(java.time.Duration.ofHours(6)));
1292
+ createdField.setStatistics(dateStats);
1293
+
1294
+ fields.add(createdField);
1295
+
1296
+ // Numeric field with business logic
1297
+ AssetSchemaDTO.SchemaFieldDTO totalSpentField = new AssetSchemaDTO.SchemaFieldDTO();
1298
+ totalSpentField.setFieldName("total_spent");
1299
+ totalSpentField.setDataType("DECIMAL");
1300
+ totalSpentField.setLogicalType("CURRENCY");
1301
+ totalSpentField.setNullable(true);
1302
+ totalSpentField.setDefaultValue("0.00");
1303
+ totalSpentField.setDescription("Total amount spent by customer");
1304
+ totalSpentField.setTags(Arrays.asList("FINANCIAL", "CALCULATED", "KPI"));
1305
+ totalSpentField.setOrdinalPosition(4);
1306
+ totalSpentField.setClassification("SENSITIVE");
1307
+
1308
+ // Numeric constraints
1309
+ AssetSchemaDTO.FieldConstraintsDTO numericConstraints = new AssetSchemaDTO.FieldConstraintsDTO();
1310
+ numericConstraints.setMinValue("0.00");
1311
+ numericConstraints.setMaxValue("999999.99");
1312
+ Map<String, Object> customConstraints = new HashMap<>();
1313
+ customConstraints.put("precision", 10);
1314
+ customConstraints.put("scale", 2);
1315
+ customConstraints.put("currency", "USD");
1316
+ numericConstraints.setCustomConstraints(customConstraints);
1317
+ totalSpentField.setConstraints(numericConstraints);
1318
+
1319
+ // Numeric statistics
1320
+ AssetSchemaDTO.FieldStatisticsDTO numericStats = new AssetSchemaDTO.FieldStatisticsDTO();
1321
+ numericStats.setDistinctCount(85000L);
1322
+ numericStats.setNullCount(1250L);
1323
+ numericStats.setFillRate(99.0);
1324
+ numericStats.setMinValue("0.00");
1325
+ numericStats.setMaxValue("45678.99");
1326
+ numericStats.setLastAnalyzed(Instant.now().minus(java.time.Duration.ofHours(6)));
1327
+ totalSpentField.setStatistics(numericStats);
1328
+
1329
+ fields.add(totalSpentField);
1330
+
1331
+ // Categorical field with enum values
1332
+ AssetSchemaDTO.SchemaFieldDTO statusField = new AssetSchemaDTO.SchemaFieldDTO();
1333
+ statusField.setFieldName("status");
1334
+ statusField.setDataType("VARCHAR");
1335
+ statusField.setLogicalType("ENUM");
1336
+ statusField.setNullable(false);
1337
+ statusField.setDefaultValue("ACTIVE");
1338
+ statusField.setDescription("Customer account status");
1339
+ statusField.setTags(Arrays.asList("STATUS", "ENUM", "BUSINESS_RULE"));
1340
+ statusField.setOrdinalPosition(5);
1341
+ statusField.setClassification("PUBLIC");
1342
+
1343
+ // Enum constraints
1344
+ AssetSchemaDTO.FieldConstraintsDTO enumConstraints = new AssetSchemaDTO.FieldConstraintsDTO();
1345
+ enumConstraints.setEnumValues(Arrays.asList("ACTIVE", "INACTIVE", "SUSPENDED", "CLOSED"));
1346
+ enumConstraints.setMaxLength(10);
1347
+ statusField.setConstraints(enumConstraints);
1348
+
1349
+ // Status statistics
1350
+ AssetSchemaDTO.FieldStatisticsDTO statusStats = new AssetSchemaDTO.FieldStatisticsDTO();
1351
+ statusStats.setDistinctCount(4L);
1352
+ statusStats.setNullCount(0L);
1353
+ statusStats.setFillRate(100.0);
1354
+ statusStats.setTopValues(Arrays.asList("ACTIVE (85%)", "INACTIVE (10%)", "SUSPENDED (3%)", "CLOSED (2%)"));
1355
+ statusStats.setLastAnalyzed(Instant.now().minus(java.time.Duration.ofHours(6)));
1356
+ statusField.setStatistics(statusStats);
1357
+
1358
+ fields.add(statusField);
1359
+
1360
+ return fields;
1361
+ }
1362
+
1363
+ /**
1364
+ * Creates schema-level constraints including primary keys, foreign keys, and check constraints.
1365
+ */
1366
+ private List<AssetSchemaDTO.SchemaConstraintDTO> createSchemaConstraints() {
1367
+ List<AssetSchemaDTO.SchemaConstraintDTO> constraints = new ArrayList<>();
1368
+
1369
+ // Primary key constraint
1370
+ AssetSchemaDTO.SchemaConstraintDTO pkConstraint = new AssetSchemaDTO.SchemaConstraintDTO();
1371
+ pkConstraint.setConstraintName("pk_customer_id");
1372
+ pkConstraint.setConstraintType("PRIMARY_KEY");
1373
+ pkConstraint.setColumnNames(Arrays.asList("customer_id"));
1374
+ pkConstraint.setEnforced(true);
1375
+ constraints.add(pkConstraint);
1376
+
1377
+ // Unique constraint on email
1378
+ AssetSchemaDTO.SchemaConstraintDTO uniqueEmailConstraint = new AssetSchemaDTO.SchemaConstraintDTO();
1379
+ uniqueEmailConstraint.setConstraintName("uk_customer_email");
1380
+ uniqueEmailConstraint.setConstraintType("UNIQUE");
1381
+ uniqueEmailConstraint.setColumnNames(Arrays.asList("email"));
1382
+ uniqueEmailConstraint.setEnforced(true);
1383
+ constraints.add(uniqueEmailConstraint);
1384
+
1385
+ // Check constraint on total_spent
1386
+ AssetSchemaDTO.SchemaConstraintDTO checkConstraint = new AssetSchemaDTO.SchemaConstraintDTO();
1387
+ checkConstraint.setConstraintName("chk_total_spent_positive");
1388
+ checkConstraint.setConstraintType("CHECK");
1389
+ checkConstraint.setColumnNames(Arrays.asList("total_spent"));
1390
+ checkConstraint.setCheckExpression("total_spent >= 0");
1391
+ checkConstraint.setEnforced(true);
1392
+ constraints.add(checkConstraint);
1393
+
1394
+ // Foreign key constraint (example)
1395
+ AssetSchemaDTO.SchemaConstraintDTO fkConstraint = new AssetSchemaDTO.SchemaConstraintDTO();
1396
+ fkConstraint.setConstraintName("fk_customer_segment");
1397
+ fkConstraint.setConstraintType("FOREIGN_KEY");
1398
+ fkConstraint.setColumnNames(Arrays.asList("segment_id"));
1399
+ fkConstraint.setReferencedTable("customer_segments");
1400
+ fkConstraint.setReferencedColumns(Arrays.asList("segment_id"));
1401
+ fkConstraint.setEnforced(true);
1402
+ constraints.add(fkConstraint);
1403
+
1404
+ return constraints;
1405
+ }
1406
+
1407
+ /**
1408
+ * Creates schema indexes for performance optimization.
1409
+ */
1410
+ private List<AssetSchemaDTO.SchemaIndexDTO> createSchemaIndexes() {
1411
+ List<AssetSchemaDTO.SchemaIndexDTO> indexes = new ArrayList<>();
1412
+
1413
+ // Primary key index
1414
+ AssetSchemaDTO.SchemaIndexDTO pkIndex = new AssetSchemaDTO.SchemaIndexDTO();
1415
+ pkIndex.setIndexName("idx_customer_id_pk");
1416
+ pkIndex.setIndexType("BTREE");
1417
+ pkIndex.setColumnNames(Arrays.asList("customer_id"));
1418
+ pkIndex.setUnique(true);
1419
+ pkIndex.setPrimary(true);
1420
+ pkIndex.setCardinality(125000L);
1421
+ indexes.add(pkIndex);
1422
+
1423
+ // Email unique index
1424
+ AssetSchemaDTO.SchemaIndexDTO emailIndex = new AssetSchemaDTO.SchemaIndexDTO();
1425
+ emailIndex.setIndexName("idx_customer_email_unique");
1426
+ emailIndex.setIndexType("BTREE");
1427
+ emailIndex.setColumnNames(Arrays.asList("email"));
1428
+ emailIndex.setUnique(true);
1429
+ emailIndex.setPrimary(false);
1430
+ emailIndex.setCardinality(124500L);
1431
+ indexes.add(emailIndex);
1432
+
1433
+ // Status index for filtering
1434
+ AssetSchemaDTO.SchemaIndexDTO statusIndex = new AssetSchemaDTO.SchemaIndexDTO();
1435
+ statusIndex.setIndexName("idx_customer_status");
1436
+ statusIndex.setIndexType("BTREE");
1437
+ statusIndex.setColumnNames(Arrays.asList("status"));
1438
+ statusIndex.setUnique(false);
1439
+ statusIndex.setPrimary(false);
1440
+ statusIndex.setCardinality(4L);
1441
+ indexes.add(statusIndex);
1442
+
1443
+ // Composite index for date range queries
1444
+ AssetSchemaDTO.SchemaIndexDTO dateRangeIndex = new AssetSchemaDTO.SchemaIndexDTO();
1445
+ dateRangeIndex.setIndexName("idx_customer_created_status");
1446
+ dateRangeIndex.setIndexType("BTREE");
1447
+ dateRangeIndex.setColumnNames(Arrays.asList("created_date", "status"));
1448
+ dateRangeIndex.setUnique(false);
1449
+ dateRangeIndex.setPrimary(false);
1450
+ dateRangeIndex.setCardinality(1460L); // 365 days * 4 statuses
1451
+ Map<String, Object> indexProps = new HashMap<>();
1452
+ indexProps.put("usage", "date_range_filtering");
1453
+ indexProps.put("selectivity", "high");
1454
+ dateRangeIndex.setProperties(indexProps);
1455
+ indexes.add(dateRangeIndex);
1456
+
1457
+ return indexes;
1458
+ }
1459
+
1460
+ /**
1461
+ * Creates comprehensive schema-level statistics.
1462
+ */
1463
+ private AssetSchemaDTO.SchemaStatisticsDTO createSchemaStatistics() {
1464
+ AssetSchemaDTO.SchemaStatisticsDTO statistics = new AssetSchemaDTO.SchemaStatisticsDTO();
1465
+
1466
+ statistics.setTotalRows(125000L);
1467
+ statistics.setTotalColumns(5L);
1468
+ statistics.setTotalSize(52428800L); // 50MB
1469
+ statistics.setCompressionRatio(0.75); // 75% compression
1470
+ statistics.setLastUpdated(Instant.now().minus(java.time.Duration.ofDays(1)));
1471
+ statistics.setLastAnalyzed(Instant.now().minus(java.time.Duration.ofHours(6)));
1472
+
1473
+ Map<String, Long> dataTypeCounts = new HashMap<>();
1474
+ dataTypeCounts.put("BIGINT", 1L);
1475
+ dataTypeCounts.put("VARCHAR", 2L);
1476
+ dataTypeCounts.put("DATE", 1L);
1477
+ dataTypeCounts.put("DECIMAL", 1L);
1478
+ statistics.setDataTypeCounts(dataTypeCounts);
1479
+
1480
+ statistics.setSchemaComplexity(2.5); // Medium complexity
1481
+ statistics.setSchemaDepth(1); // Flat table structure
1482
+
1483
+ return statistics;
1484
+ }
1485
+
1486
+ /**
1487
+ * Creates schema lineage information showing relationships and transformations.
1488
+ */
1489
+ private AssetSchemaDTO.SchemaLineageDTO createSchemaLineage(Asset asset) {
1490
+ AssetSchemaDTO.SchemaLineageDTO lineage = new AssetSchemaDTO.SchemaLineageDTO();
1491
+
1492
+ // Upstream relationships
1493
+ List<AssetSchemaDTO.SchemaRelationshipDTO> upstream = new ArrayList<>();
1494
+
1495
+ AssetSchemaDTO.SchemaRelationshipDTO upstreamRel = new AssetSchemaDTO.SchemaRelationshipDTO();
1496
+ upstreamRel.setRelatedAssetId(UUID.randomUUID());
1497
+ upstreamRel.setRelatedAssetName("raw_customer_data");
1498
+ upstreamRel.setRelationshipType("DERIVED_FROM");
1499
+ upstreamRel.setTransformationType("ETL");
1500
+ upstreamRel.setRelationshipCreated(Instant.now().minus(java.time.Duration.ofDays(30)));
1501
+
1502
+ // Field mappings
1503
+ List<AssetSchemaDTO.FieldMappingDTO> fieldMappings = new ArrayList<>();
1504
+ fieldMappings.add(new AssetSchemaDTO.FieldMappingDTO("cust_id", "customer_id", "DIRECT"));
1505
+ fieldMappings.add(new AssetSchemaDTO.FieldMappingDTO("email_addr", "email", "TRANSFORMED"));
1506
+ fieldMappings.add(new AssetSchemaDTO.FieldMappingDTO("create_ts", "created_date", "TRANSFORMED"));
1507
+ upstreamRel.setFieldMappings(fieldMappings);
1508
+
1509
+ upstream.add(upstreamRel);
1510
+ lineage.setUpstream(upstream);
1511
+
1512
+ // Downstream relationships
1513
+ List<AssetSchemaDTO.SchemaRelationshipDTO> downstream = new ArrayList<>();
1514
+
1515
+ AssetSchemaDTO.SchemaRelationshipDTO downstreamRel = new AssetSchemaDTO.SchemaRelationshipDTO();
1516
+ downstreamRel.setRelatedAssetId(UUID.randomUUID());
1517
+ downstreamRel.setRelatedAssetName("customer_analytics_mart");
1518
+ downstreamRel.setRelationshipType("FEEDS_INTO");
1519
+ downstreamRel.setTransformationType("AGGREGATION");
1520
+ downstreamRel.setRelationshipCreated(Instant.now().minus(java.time.Duration.ofDays(15)));
1521
+
1522
+ List<AssetSchemaDTO.FieldMappingDTO> downstreamMappings = new ArrayList<>();
1523
+ downstreamMappings.add(new AssetSchemaDTO.FieldMappingDTO("customer_id", "customer_key", "DIRECT"));
1524
+ downstreamMappings.add(new AssetSchemaDTO.FieldMappingDTO("total_spent", "lifetime_value", "CALCULATED"));
1525
+ downstreamRel.setFieldMappings(downstreamMappings);
1526
+
1527
+ downstream.add(downstreamRel);
1528
+ lineage.setDownstream(downstream);
1529
+
1530
+ // Transformations
1531
+ List<AssetSchemaDTO.SchemaTransformationDTO> transformations = new ArrayList<>();
1532
+
1533
+ AssetSchemaDTO.SchemaTransformationDTO transformation = new AssetSchemaDTO.SchemaTransformationDTO();
1534
+ transformation.setTransformationId("etl_customer_cleansing_v2.1");
1535
+ transformation.setTransformationType("ETL");
1536
+ transformation.setTransformationLogic("Data cleansing, deduplication, and standardization");
1537
+ transformation.setInputFields(Arrays.asList("cust_id", "email_addr", "create_ts", "spent_total", "acct_status"));
1538
+ transformation.setOutputFields(Arrays.asList("customer_id", "email", "created_date", "total_spent", "status"));
1539
+
1540
+ Map<String, Object> parameters = new HashMap<>();
1541
+ parameters.put("deduplication_key", "email_addr");
1542
+ parameters.put("data_quality_threshold", 0.95);
1543
+ parameters.put("transformation_version", "2.1.0");
1544
+ transformation.setParameters(parameters);
1545
+ transformation.setExecutedAt(Instant.now().minus(java.time.Duration.ofHours(12)));
1546
+
1547
+ transformations.add(transformation);
1548
+ lineage.setTransformations(transformations);
1549
+
1550
+ lineage.setLineageDepth(3);
1551
+ lineage.setLineageLastUpdated(Instant.now().minus(java.time.Duration.ofHours(6)));
1552
+
1553
+ return lineage;
1554
+ }
1555
+
1556
+ /**
1557
+ * Creates schema evolution tracking with version history and change tracking.
1558
+ */
1559
+ private AssetSchemaDTO.SchemaEvolutionDTO createSchemaEvolution() {
1560
+ AssetSchemaDTO.SchemaEvolutionDTO evolution = new AssetSchemaDTO.SchemaEvolutionDTO();
1561
+
1562
+ // Version history
1563
+ List<AssetSchemaDTO.SchemaVersionDTO> versions = new ArrayList<>();
1564
+
1565
+ AssetSchemaDTO.SchemaVersionDTO v1 = new AssetSchemaDTO.SchemaVersionDTO();
1566
+ v1.setVersion("1.0.0");
1567
+ v1.setCreatedAt(Instant.now().minus(java.time.Duration.ofDays(180)));
1568
+ v1.setCreatedBy("data.engineer@company.com");
1569
+ v1.setChangeDescription("Initial schema creation");
1570
+ v1.setFieldCount(3);
1571
+ v1.setChangedFields(Arrays.asList("customer_id", "email", "created_date"));
1572
+ versions.add(v1);
1573
+
1574
+ AssetSchemaDTO.SchemaVersionDTO v2 = new AssetSchemaDTO.SchemaVersionDTO();
1575
+ v2.setVersion("2.0.0");
1576
+ v2.setCreatedAt(Instant.now().minus(java.time.Duration.ofDays(90)));
1577
+ v2.setCreatedBy("data.architect@company.com");
1578
+ v2.setChangeDescription("Added financial and status fields");
1579
+ v2.setFieldCount(5);
1580
+ v2.setChangedFields(Arrays.asList("total_spent", "status"));
1581
+ versions.add(v2);
1582
+
1583
+ AssetSchemaDTO.SchemaVersionDTO v21 = new AssetSchemaDTO.SchemaVersionDTO();
1584
+ v21.setVersion("2.1.0");
1585
+ v21.setCreatedAt(Instant.now().minus(java.time.Duration.ofDays(2)));
1586
+ v21.setCreatedBy("data.steward@company.com");
1587
+ v21.setChangeDescription("Enhanced constraints and data quality rules");
1588
+ v21.setFieldCount(5);
1589
+ v21.setChangedFields(Arrays.asList("email", "total_spent"));
1590
+ versions.add(v21);
1591
+
1592
+ evolution.setVersions(versions);
1593
+
1594
+ // Recent changes
1595
+ List<AssetSchemaDTO.SchemaChangeDTO> recentChanges = new ArrayList<>();
1596
+
1597
+ AssetSchemaDTO.SchemaChangeDTO change1 = new AssetSchemaDTO.SchemaChangeDTO();
1598
+ change1.setChangeType("CONSTRAINT_ADDED");
1599
+ change1.setFieldName("email");
1600
+ change1.setOldValue("VARCHAR(255)");
1601
+ change1.setNewValue("VARCHAR(255) WITH EMAIL_VALIDATION");
1602
+ change1.setChangeTimestamp(Instant.now().minus(java.time.Duration.ofDays(2)));
1603
+ change1.setChangedBy("data.steward@company.com");
1604
+ change1.setChangeReason("Enhanced data quality validation");
1605
+ recentChanges.add(change1);
1606
+
1607
+ AssetSchemaDTO.SchemaChangeDTO change2 = new AssetSchemaDTO.SchemaChangeDTO();
1608
+ change2.setChangeType("CONSTRAINT_MODIFIED");
1609
+ change2.setFieldName("total_spent");
1610
+ change2.setOldValue("CHECK(total_spent >= 0)");
1611
+ change2.setNewValue("CHECK(total_spent >= 0 AND total_spent <= 999999.99)");
1612
+ change2.setChangeTimestamp(Instant.now().minus(java.time.Duration.ofDays(1)));
1613
+ change2.setChangedBy("data.steward@company.com");
1614
+ change2.setChangeReason("Added upper bound for data quality");
1615
+ recentChanges.add(change2);
1616
+
1617
+ evolution.setRecentChanges(recentChanges);
1618
+ evolution.setTotalVersions(3);
1619
+ evolution.setFirstVersion(Instant.now().minus(java.time.Duration.ofDays(180)));
1620
+ evolution.setLastChange(Instant.now().minus(java.time.Duration.ofDays(1)));
1621
+
1622
+ return evolution;
1623
+ }
1624
+
1625
+ /**
1626
+ * Creates business metadata response for enhanced business context management.
1627
+ */
1628
+ private BusinessMetadataResponseDTO createBusinessMetadataResponse(Asset asset, BusinessMetadataInputDTO input) {
1629
+ // Implementation for business metadata response
1630
+ // This would integrate with the comprehensive BusinessMetadataResponseDTO created earlier
1631
+ BusinessMetadataResponseDTO response = new BusinessMetadataResponseDTO();
1632
+ // Detailed implementation would follow the existing pattern
1633
+ return response;
1634
+ }
1635
+
1636
+ @Override
1637
+ public EnhancedLineageDTO getEnhancedLineage(UUID assetId, Integer maxDepth) {
1638
+ // Check if asset exists
1639
+ if (!assetRepository.existsById(assetId)) {
1640
+ throw new ResourceNotFoundException("Asset", "id", assetId.toString());
1641
+ }
1642
+
1643
+ Asset asset = assetRepository.findById(assetId)
1644
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
1645
+
1646
+ // Use default depth if not specified
1647
+ int depth = maxDepth != null ? maxDepth : 5;
1648
+
1649
+ return createEnhancedLineage(asset, depth);
1650
+ }
1651
+
1652
+ @Override
1653
+ public AssetPolicyMappingDTO getAssetPolicyMapping(UUID assetId) {
1654
+ // Check if asset exists
1655
+ if (!assetRepository.existsById(assetId)) {
1656
+ throw new ResourceNotFoundException("Asset", "id", assetId.toString());
1657
+ }
1658
+
1659
+ Asset asset = assetRepository.findById(assetId)
1660
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
1661
+
1662
+ return createAssetPolicyMapping(asset);
1663
+ }
1664
+
1665
+ @Override
1666
+ public CatalogFiltersDTO getCatalogFilters() {
1667
+ return createCatalogFilters();
1668
+ }
1669
+
1670
+ @Override
1671
+ public AssetComplianceStatusDTO getAssetComplianceStatus(UUID assetId) {
1672
+ // Check if asset exists
1673
+ if (!assetRepository.existsById(assetId)) {
1674
+ throw new ResourceNotFoundException("Asset", "id", assetId.toString());
1675
+ }
1676
+
1677
+ Asset asset = assetRepository.findById(assetId)
1678
+ .orElseThrow(() -> new ResourceNotFoundException("Asset", "id", assetId.toString()));
1679
+
1680
+ return createAssetComplianceStatus(asset);
1681
+ }
1682
+
1683
+ // Helper methods for new Priority 2 endpoints
1684
+ private EnhancedLineageDTO createEnhancedLineage(Asset asset, int maxDepth) {
1685
+ EnhancedLineageDTO lineage = new EnhancedLineageDTO(asset.getAssetId(), asset.getAssetName());
1686
+
1687
+ // Create lineage graph with realistic mock data
1688
+ EnhancedLineageDTO.LineageGraphDTO graph = new EnhancedLineageDTO.LineageGraphDTO();
1689
+
1690
+ // Create nodes for demonstration
1691
+ List<EnhancedLineageDTO.LineageNodeDTO> nodes = new ArrayList<>();
1692
+ List<EnhancedLineageDTO.LineageEdgeDTO> edges = new ArrayList<>();
1693
+
1694
+ // Add current asset as central node
1695
+ EnhancedLineageDTO.LineageNodeDTO centralNode = new EnhancedLineageDTO.LineageNodeDTO(
1696
+ asset.getAssetId(), asset.getAssetName(), "ASSET");
1697
+ centralNode.setAssetType(asset.getAssetType());
1698
+ centralNode.setStatus("ACTIVE");
1699
+ centralNode.setLayer(0);
1700
+ centralNode.setIcon("table");
1701
+ centralNode.setColor("#4A90E2");
1702
+
1703
+ EnhancedLineageDTO.NodeStatsDTO centralStats = new EnhancedLineageDTO.NodeStatsDTO();
1704
+ centralStats.setRecordCount(250000L);
1705
+ centralStats.setSizeBytes(52428800L);
1706
+ centralStats.setLastAccessed(Instant.now().minus(2, ChronoUnit.HOURS));
1707
+ centralStats.setLastModified(Instant.now().minus(1, ChronoUnit.DAYS));
1708
+ centralStats.setUsageFrequency(45);
1709
+ centralStats.setHealthStatus("HEALTHY");
1710
+ centralNode.setStatistics(centralStats);
1711
+
1712
+ nodes.add(centralNode);
1713
+
1714
+ // Add upstream assets
1715
+ for (int i = 1; i <= Math.min(maxDepth, 3); i++) {
1716
+ UUID upstreamId = UUID.randomUUID();
1717
+ EnhancedLineageDTO.LineageNodeDTO upstreamNode = new EnhancedLineageDTO.LineageNodeDTO(
1718
+ upstreamId, "upstream_source_" + i, "ASSET");
1719
+ upstreamNode.setAssetType("TABLE");
1720
+ upstreamNode.setStatus("ACTIVE");
1721
+ upstreamNode.setLayer(-i);
1722
+ upstreamNode.setIcon("database");
1723
+ upstreamNode.setColor("#7ED321");
1724
+ nodes.add(upstreamNode);
1725
+
1726
+ // Add edge from upstream to central
1727
+ EnhancedLineageDTO.LineageEdgeDTO edge = new EnhancedLineageDTO.LineageEdgeDTO(
1728
+ "edge_" + upstreamId, upstreamId, asset.getAssetId(), "DERIVED_FROM");
1729
+ edge.setTransformationType("ETL");
1730
+ edge.setExecutionStatus("SUCCESS");
1731
+ edge.setStyle("solid");
1732
+ edge.setColor("#666666");
1733
+ edges.add(edge);
1734
+ }
1735
+
1736
+ // Add downstream assets
1737
+ for (int i = 1; i <= Math.min(maxDepth, 2); i++) {
1738
+ UUID downstreamId = UUID.randomUUID();
1739
+ EnhancedLineageDTO.LineageNodeDTO downstreamNode = new EnhancedLineageDTO.LineageNodeDTO(
1740
+ downstreamId, "downstream_target_" + i, "ASSET");
1741
+ downstreamNode.setAssetType("VIEW");
1742
+ downstreamNode.setStatus("ACTIVE");
1743
+ downstreamNode.setLayer(i);
1744
+ downstreamNode.setIcon("view");
1745
+ downstreamNode.setColor("#F5A623");
1746
+ nodes.add(downstreamNode);
1747
+
1748
+ // Add edge from central to downstream
1749
+ EnhancedLineageDTO.LineageEdgeDTO edge = new EnhancedLineageDTO.LineageEdgeDTO(
1750
+ "edge_" + downstreamId, asset.getAssetId(), downstreamId, "WRITES_TO");
1751
+ edge.setTransformationType("AGGREGATE");
1752
+ edge.setExecutionStatus("SUCCESS");
1753
+ edge.setStyle("solid");
1754
+ edge.setColor("#666666");
1755
+ edges.add(edge);
1756
+ }
1757
+
1758
+ graph.setNodes(nodes);
1759
+ graph.setEdges(edges);
1760
+ graph.setTotalNodes(nodes.size());
1761
+ graph.setTotalEdges(edges.size());
1762
+ graph.setMaxDepth(maxDepth);
1763
+ lineage.setLineageGraph(graph);
1764
+
1765
+ // Add statistics
1766
+ EnhancedLineageDTO.LineageStatisticsDTO stats = new EnhancedLineageDTO.LineageStatisticsDTO();
1767
+ stats.setTotalUpstreamAssets(3);
1768
+ stats.setTotalDownstreamAssets(2);
1769
+ stats.setTotalTransformations(5);
1770
+ stats.setDirectDependencies(3);
1771
+ stats.setIndirectDependencies(2);
1772
+ stats.setLineageCompleteness(85.5);
1773
+ stats.setLineageHealth("HEALTHY");
1774
+ lineage.setStatistics(stats);
1775
+
1776
+ lineage.setLastUpdated(Instant.now());
1777
+ lineage.setLineageVersion("1.2.0");
1778
+ lineage.setMaxDepth(maxDepth);
1779
+
1780
+ return lineage;
1781
+ }
1782
+
1783
+ private AssetPolicyMappingDTO createAssetPolicyMapping(Asset asset) {
1784
+ AssetPolicyMappingDTO mapping = new AssetPolicyMappingDTO(asset.getAssetId(), asset.getAssetName());
1785
+
1786
+ // Create applicable policies
1787
+ List<AssetPolicyMappingDTO.PolicyMappingDTO> applicablePolicies = new ArrayList<>();
1788
+
1789
+ AssetPolicyMappingDTO.PolicyMappingDTO policy1 = new AssetPolicyMappingDTO.PolicyMappingDTO(
1790
+ UUID.randomUUID(), "Data Retention Policy", "DATA_GOVERNANCE");
1791
+ policy1.setPolicyVersion("1.0");
1792
+ policy1.setApplicabilityReason("TYPE_BASED");
1793
+ policy1.setComplianceStatus("COMPLIANT");
1794
+ policy1.setComplianceScore(95.0);
1795
+ policy1.setLastEvaluated(Instant.now().minus(1, ChronoUnit.HOURS));
1796
+ applicablePolicies.add(policy1);
1797
+
1798
+ AssetPolicyMappingDTO.PolicyMappingDTO policy2 = new AssetPolicyMappingDTO.PolicyMappingDTO(
1799
+ UUID.randomUUID(), "PII Protection Policy", "SECURITY");
1800
+ policy2.setPolicyVersion("2.1");
1801
+ policy2.setApplicabilityReason("LABEL_BASED");
1802
+ policy2.setComplianceStatus("PARTIALLY_COMPLIANT");
1803
+ policy2.setComplianceScore(78.5);
1804
+ policy2.setLastEvaluated(Instant.now().minus(2, ChronoUnit.HOURS));
1805
+ applicablePolicies.add(policy2);
1806
+
1807
+ mapping.setApplicablePolicies(applicablePolicies);
1808
+
1809
+ // Create compliance summary
1810
+ AssetPolicyMappingDTO.PolicyComplianceSummaryDTO summary = new AssetPolicyMappingDTO.PolicyComplianceSummaryDTO();
1811
+ summary.setTotalPolicies(2);
1812
+ summary.setCompliantPolicies(1);
1813
+ summary.setNonCompliantPolicies(0);
1814
+ summary.setPartiallyCompliantPolicies(1);
1815
+ summary.setOverallComplianceScore(86.75);
1816
+ summary.setOverallStatus("PARTIALLY_COMPLIANT");
1817
+ summary.setLastFullEvaluation(Instant.now().minus(1, ChronoUnit.HOURS));
1818
+ mapping.setComplianceSummary(summary);
1819
+
1820
+ mapping.setLastEvaluated(Instant.now().minus(30, ChronoUnit.MINUTES));
1821
+ mapping.setEvaluationStatus("COMPLETED");
1822
+
1823
+ return mapping;
1824
+ }
1825
+
1826
+ private CatalogFiltersDTO createCatalogFilters() {
1827
+ CatalogFiltersDTO filters = new CatalogFiltersDTO();
1828
+
1829
+ List<CatalogFiltersDTO.FilterCategoryDTO> categories = new ArrayList<>();
1830
+
1831
+ // Asset Type filter
1832
+ CatalogFiltersDTO.FilterCategoryDTO assetTypeCategory = new CatalogFiltersDTO.FilterCategoryDTO(
1833
+ "assetType", "Asset Type", "MULTI_SELECT");
1834
+ List<CatalogFiltersDTO.FilterOptionDTO> assetTypeOptions = Arrays.asList(
1835
+ new CatalogFiltersDTO.FilterOptionDTO("TABLE", "Tables", 1250),
1836
+ new CatalogFiltersDTO.FilterOptionDTO("VIEW", "Views", 340),
1837
+ new CatalogFiltersDTO.FilterOptionDTO("FILE", "Files", 890),
1838
+ new CatalogFiltersDTO.FilterOptionDTO("API", "APIs", 125),
1839
+ new CatalogFiltersDTO.FilterOptionDTO("STREAM", "Streams", 67)
1840
+ );
1841
+ assetTypeCategory.setOptions(assetTypeOptions);
1842
+ assetTypeCategory.setIsExpandedByDefault(true);
1843
+ assetTypeCategory.setDisplayOrder(1);
1844
+ categories.add(assetTypeCategory);
1845
+
1846
+ // Cloud Provider filter
1847
+ CatalogFiltersDTO.FilterCategoryDTO providerCategory = new CatalogFiltersDTO.FilterCategoryDTO(
1848
+ "cloudProvider", "Cloud Provider", "MULTI_SELECT");
1849
+ List<CatalogFiltersDTO.FilterOptionDTO> providerOptions = Arrays.asList(
1850
+ new CatalogFiltersDTO.FilterOptionDTO("AWS", "Amazon Web Services", 1100),
1851
+ new CatalogFiltersDTO.FilterOptionDTO("AZURE", "Microsoft Azure", 750),
1852
+ new CatalogFiltersDTO.FilterOptionDTO("GCP", "Google Cloud Platform", 450),
1853
+ new CatalogFiltersDTO.FilterOptionDTO("ON_PREMISE", "On-Premise", 372)
1854
+ );
1855
+ providerCategory.setOptions(providerOptions);
1856
+ providerCategory.setIsExpandedByDefault(true);
1857
+ providerCategory.setDisplayOrder(2);
1858
+ categories.add(providerCategory);
1859
+
1860
+ // Labels filter
1861
+ CatalogFiltersDTO.FilterCategoryDTO labelsCategory = new CatalogFiltersDTO.FilterCategoryDTO(
1862
+ "labels", "Labels", "MULTI_SELECT");
1863
+ List<CatalogFiltersDTO.FilterOptionDTO> labelOptions = Arrays.asList(
1864
+ new CatalogFiltersDTO.FilterOptionDTO("PII", "Personally Identifiable Information", 234),
1865
+ new CatalogFiltersDTO.FilterOptionDTO("SENSITIVE", "Sensitive Data", 567),
1866
+ new CatalogFiltersDTO.FilterOptionDTO("PUBLIC", "Public Data", 1890),
1867
+ new CatalogFiltersDTO.FilterOptionDTO("FINANCIAL", "Financial Data", 123),
1868
+ new CatalogFiltersDTO.FilterOptionDTO("CUSTOMER", "Customer Data", 456)
1869
+ );
1870
+ labelsCategory.setOptions(labelOptions);
1871
+ labelsCategory.setIsExpandedByDefault(false);
1872
+ labelsCategory.setDisplayOrder(3);
1873
+ categories.add(labelsCategory);
1874
+
1875
+ filters.setFilterCategories(categories);
1876
+
1877
+ // Quick filters
1878
+ Map<String, List<String>> quickFilters = new HashMap<>();
1879
+ quickFilters.put("recent", Arrays.asList("Last 7 days", "Last 30 days", "Last 90 days"));
1880
+ quickFilters.put("popular", Arrays.asList("Most accessed", "Most queried", "Trending"));
1881
+ filters.setQuickFilters(quickFilters);
1882
+
1883
+ // Search options
1884
+ CatalogFiltersDTO.SearchOptionsDTO searchOptions = new CatalogFiltersDTO.SearchOptionsDTO();
1885
+ searchOptions.setSearchableFields(Arrays.asList("assetName", "description", "labels", "metadata"));
1886
+ searchOptions.setSortableFields(Arrays.asList("assetName", "createdTs", "lastModifiedTs", "size"));
1887
+ searchOptions.setMaxResults(1000);
1888
+ searchOptions.setDefaultPageSize(20);
1889
+ searchOptions.setSupportsFullTextSearch(true);
1890
+ searchOptions.setSupportsWildcards(true);
1891
+ searchOptions.setSupportsFuzzySearch(true);
1892
+ filters.setSearchOptions(searchOptions);
1893
+
1894
+ filters.setLastUpdated(Instant.now());
1895
+ filters.setTotalAssets(2672);
1896
+ filters.setCatalogVersion("2.1.0");
1897
+
1898
+ return filters;
1899
+ }
1900
+
1901
+ private AssetComplianceStatusDTO createAssetComplianceStatus(Asset asset) {
1902
+ AssetComplianceStatusDTO compliance = new AssetComplianceStatusDTO(asset.getAssetId(), asset.getAssetName());
1903
+
1904
+ compliance.setOverallComplianceStatus("PARTIALLY_COMPLIANT");
1905
+ compliance.setOverallComplianceScore(82.5);
1906
+
1907
+ // Framework compliance
1908
+ List<AssetComplianceStatusDTO.ComplianceFrameworkStatusDTO> frameworks = new ArrayList<>();
1909
+
1910
+ AssetComplianceStatusDTO.ComplianceFrameworkStatusDTO gdpr = new AssetComplianceStatusDTO.ComplianceFrameworkStatusDTO(
1911
+ "GDPR", "COMPLIANT", 94.0);
1912
+ gdpr.setFrameworkVersion("2018.1");
1913
+ gdpr.setTotalRequirements(15);
1914
+ gdpr.setMetRequirements(14);
1915
+ gdpr.setLastAssessment(Instant.now().minus(7, ChronoUnit.DAYS));
1916
+ frameworks.add(gdpr);
1917
+
1918
+ AssetComplianceStatusDTO.ComplianceFrameworkStatusDTO sox = new AssetComplianceStatusDTO.ComplianceFrameworkStatusDTO(
1919
+ "SOX", "PARTIALLY_COMPLIANT", 71.0);
1920
+ sox.setFrameworkVersion("2002.1");
1921
+ sox.setTotalRequirements(12);
1922
+ sox.setMetRequirements(8);
1923
+ sox.setLastAssessment(Instant.now().minus(3, ChronoUnit.DAYS));
1924
+ frameworks.add(sox);
1925
+
1926
+ compliance.setFrameworkCompliance(frameworks);
1927
+
1928
+ // Violations
1929
+ List<AssetComplianceStatusDTO.ComplianceViolationDTO> violations = new ArrayList<>();
1930
+
1931
+ AssetComplianceStatusDTO.ComplianceViolationDTO violation1 = new AssetComplianceStatusDTO.ComplianceViolationDTO(
1932
+ "VIO_001", "ACCESS_CONTROL", "MEDIUM");
1933
+ violation1.setDescription("Insufficient access controls for sensitive data fields");
1934
+ violation1.setFramework("SOX");
1935
+ violation1.setStatus("OPEN");
1936
+ violation1.setDetectedAt(Instant.now().minus(2, ChronoUnit.DAYS));
1937
+ violations.add(violation1);
1938
+
1939
+ compliance.setViolations(violations);
1940
+
1941
+ // Risk assessment
1942
+ AssetComplianceStatusDTO.ComplianceRiskAssessmentDTO risk = new AssetComplianceStatusDTO.ComplianceRiskAssessmentDTO();
1943
+ risk.setOverallRiskLevel("MEDIUM");
1944
+ risk.setRiskScore(65.0);
1945
+ risk.setRiskFactors(Arrays.asList("Contains PII", "High access volume", "Cross-border data transfers"));
1946
+ risk.setLastAssessed(Instant.now().minus(1, ChronoUnit.DAYS));
1947
+ compliance.setRiskAssessment(risk);
1948
+
1949
+ compliance.setLastEvaluated(Instant.now().minus(1, ChronoUnit.HOURS));
1950
+ compliance.setEvaluatedBy("system");
1951
+ compliance.setNextEvaluation(Instant.now().plus(7, ChronoUnit.DAYS));
1952
+
1953
+ return compliance;
1954
+ }
1955
+ }
src/main/java/com/dalab/catalog/service/IAssetService.java ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.service;
2
+
3
+ import java.util.List;
4
+ import java.util.Map;
5
+ import java.util.UUID;
6
+
7
+ import org.springframework.data.domain.Page;
8
+ import org.springframework.data.domain.Pageable;
9
+
10
+ import com.dalab.catalog.dto.AssetComplianceStatusDTO;
11
+ import com.dalab.catalog.dto.AssetInputDTO;
12
+ import com.dalab.catalog.dto.AssetLabelsPostRequestDTO;
13
+ import com.dalab.catalog.dto.AssetOutputDTO;
14
+ import com.dalab.catalog.dto.AssetPolicyMappingDTO;
15
+ import com.dalab.catalog.dto.AssetSchemaDTO;
16
+ import com.dalab.catalog.dto.AssetUsageAnalyticsDTO;
17
+ import com.dalab.catalog.dto.BusinessMetadataInputDTO;
18
+ import com.dalab.catalog.dto.BusinessMetadataResponseDTO;
19
+ import com.dalab.catalog.dto.CatalogFiltersDTO;
20
+ import com.dalab.catalog.dto.EnhancedLineageDTO;
21
+ import com.dalab.catalog.dto.LabelOutputDTO;
22
+ import com.dalab.catalog.dto.LineageResponseDTO;
23
+
24
+ public interface IAssetService {
25
+
26
+ Page<AssetOutputDTO> getAllAssets(
27
+ Pageable pageable,
28
+ String cloudProvider,
29
+ String assetType,
30
+ String region,
31
+ List<String> labels, // Filter by assets having ALL these labels
32
+ String nameContains,
33
+ UUID connectionId
34
+ // Add other filter parameters as needed
35
+ );
36
+
37
+ AssetOutputDTO getAssetById(UUID assetId);
38
+
39
+ AssetOutputDTO createAsset(AssetInputDTO assetInputDTO, UUID creatorUserId);
40
+
41
+ AssetOutputDTO updateBusinessMetadata(UUID assetId, Map<String, Object> businessMetadata, UUID updaterUserId);
42
+
43
+ /**
44
+ * Update enhanced business context and metadata for an asset.
45
+ * Supports steward relationships, compliance classification, and business context.
46
+ *
47
+ * @param assetId the ID of the asset to update
48
+ * @param businessMetadataInput enhanced business metadata with steward relationships and compliance info
49
+ * @param updaterUserId the ID of the user performing the update
50
+ * @return enhanced business metadata response with validation status and recommendations
51
+ */
52
+ BusinessMetadataResponseDTO updateBusinessContext(UUID assetId, BusinessMetadataInputDTO businessMetadataInput, UUID updaterUserId);
53
+
54
+ /**
55
+ * Get comprehensive business metadata for an asset.
56
+ * Includes business context, steward relationships, compliance classification,
57
+ * audit information, and validation status.
58
+ *
59
+ * @param assetId the ID of the asset
60
+ * @return comprehensive business metadata response with audit and validation information
61
+ */
62
+ BusinessMetadataResponseDTO getBusinessMetadata(UUID assetId);
63
+
64
+ LineageResponseDTO getAssetLineage(UUID assetId);
65
+
66
+ List<LabelOutputDTO> getAssetLabels(UUID assetId);
67
+
68
+ List<LabelOutputDTO> assignLabelsToAsset(UUID assetId, AssetLabelsPostRequestDTO labelsToAssign, UUID assignerUserId);
69
+
70
+ void removeLabelFromAsset(UUID assetId, String labelName, UUID removerUserId);
71
+
72
+ /**
73
+ * Get comprehensive usage analytics for an asset.
74
+ * Provides insights into access patterns, user behavior, performance metrics,
75
+ * and optimization recommendations.
76
+ *
77
+ * @param assetId the ID of the asset to analyze
78
+ * @return comprehensive usage analytics including access patterns, user analytics, performance metrics, and recommendations
79
+ */
80
+ AssetUsageAnalyticsDTO getAssetUsageAnalytics(UUID assetId);
81
+
82
+ /**
83
+ * Get comprehensive asset schema information.
84
+ * Priority 2 endpoint: Enhanced catalog functionality with detailed schema structure.
85
+ *
86
+ * @param assetId UUID of the asset
87
+ * @return AssetSchemaDTO with detailed schema information including fields, constraints, statistics, and lineage
88
+ */
89
+ AssetSchemaDTO getAssetSchema(UUID assetId);
90
+
91
+ /**
92
+ * Get enhanced data lineage visualization information.
93
+ * Priority 2 endpoint: Provides detailed lineage with visualization metadata for graph rendering.
94
+ *
95
+ * @param assetId UUID of the asset
96
+ * @param maxDepth Maximum depth to traverse (optional, default 5)
97
+ * @return EnhancedLineageDTO with graph nodes, edges, and visualization metadata
98
+ */
99
+ EnhancedLineageDTO getEnhancedLineage(UUID assetId, Integer maxDepth);
100
+
101
+ /**
102
+ * Get asset-policy mapping information.
103
+ * Priority 2 endpoint: Shows which policies apply to an asset and their compliance status.
104
+ *
105
+ * @param assetId UUID of the asset
106
+ * @return AssetPolicyMappingDTO with applicable policies and compliance information
107
+ */
108
+ AssetPolicyMappingDTO getAssetPolicyMapping(UUID assetId);
109
+
110
+ /**
111
+ * Get catalog faceted search filters.
112
+ * Priority 2 endpoint: Provides filter metadata for advanced search and filtering in the catalog UI.
113
+ *
114
+ * @return CatalogFiltersDTO with filter categories, options, and search capabilities
115
+ */
116
+ CatalogFiltersDTO getCatalogFilters();
117
+
118
+ /**
119
+ * Get asset compliance status information.
120
+ * Priority 2 endpoint: Provides detailed compliance status and monitoring for assets.
121
+ *
122
+ * @param assetId UUID of the asset
123
+ * @return AssetComplianceStatusDTO with framework compliance, violations, and recommendations
124
+ */
125
+ AssetComplianceStatusDTO getAssetComplianceStatus(UUID assetId);
126
+
127
+ // Placeholder for creating/updating assets, to be defined by POST/PUT API tasks
128
+ // AssetOutputDTO createAsset(AssetInputDTO assetInputDTO);
129
+ // AssetOutputDTO updateAsset(UUID assetId, AssetUpdateDTO assetUpdateDTO);
130
+ }
src/main/java/com/dalab/catalog/service/ILabelTaxonomyService.java ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.service;
2
+
3
+ import com.dalab.catalog.dto.TaxonomyLabelDTO;
4
+ import org.springframework.data.domain.Page;
5
+ import org.springframework.data.domain.Pageable;
6
+
7
+ import java.util.List;
8
+ import java.util.UUID;
9
+
10
+ public interface ILabelTaxonomyService {
11
+
12
+ Page<TaxonomyLabelDTO> getAllLabels(Pageable pageable, String category, String nameContains);
13
+
14
+ TaxonomyLabelDTO getLabelByIdOrName(String idOrName);
15
+
16
+ TaxonomyLabelDTO createLabel(TaxonomyLabelDTO labelDTO, UUID creatorUserId);
17
+
18
+ TaxonomyLabelDTO updateLabel(String idOrName, TaxonomyLabelDTO labelDTO, UUID updaterUserId);
19
+
20
+ void deleteLabel(String idOrName); // Consider implications: what happens to assets with this label?
21
+ }
src/main/java/com/dalab/catalog/service/LabelTaxonomyService.java ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.service;
2
+
3
+ import java.util.ArrayList;
4
+ import java.util.List;
5
+ import java.util.Optional;
6
+ import java.util.UUID;
7
+
8
+ import org.slf4j.Logger;
9
+ import org.slf4j.LoggerFactory;
10
+ import org.springframework.beans.factory.annotation.Autowired;
11
+ import org.springframework.dao.DataIntegrityViolationException;
12
+ import org.springframework.data.domain.Page;
13
+ import org.springframework.data.domain.Pageable;
14
+ import org.springframework.data.jpa.domain.Specification;
15
+ import org.springframework.stereotype.Service;
16
+ import org.springframework.transaction.annotation.Transactional;
17
+ import org.springframework.util.StringUtils;
18
+
19
+ import com.dalab.catalog.common.ConflictException;
20
+ import com.dalab.catalog.common.ResourceNotFoundException;
21
+ import com.dalab.catalog.dto.TaxonomyLabelDTO;
22
+ import com.dalab.catalog.mapper.LabelTaxonomyMapper;
23
+ import com.dalab.common.model.entity.Label;
24
+ import com.dalab.common.repository.IAssetLabelAssignmentRepository;
25
+ import com.dalab.common.repository.ILabelRepository;
26
+
27
+ import jakarta.persistence.criteria.Predicate;
28
+
29
+ @Service
30
+ @Transactional
31
+ public class LabelTaxonomyService implements ILabelTaxonomyService {
32
+
33
+ private static final Logger log = LoggerFactory.getLogger(LabelTaxonomyService.class);
34
+
35
+ private final ILabelRepository labelRepository;
36
+ private final LabelTaxonomyMapper labelTaxonomyMapper;
37
+ private final IAssetLabelAssignmentRepository assetLabelAssignmentRepository;
38
+
39
+ @Autowired
40
+ public LabelTaxonomyService(ILabelRepository labelRepository,
41
+ LabelTaxonomyMapper labelTaxonomyMapper,
42
+ IAssetLabelAssignmentRepository assetLabelAssignmentRepository) {
43
+ this.labelRepository = labelRepository;
44
+ this.labelTaxonomyMapper = labelTaxonomyMapper;
45
+ this.assetLabelAssignmentRepository = assetLabelAssignmentRepository;
46
+ }
47
+
48
+ @Override
49
+ @Transactional(readOnly = true)
50
+ public Page<TaxonomyLabelDTO> getAllLabels(Pageable pageable, String category, String nameContains) {
51
+ Specification<Label> spec = (root, query, criteriaBuilder) -> {
52
+ List<Predicate> predicates = new ArrayList<>();
53
+ if (StringUtils.hasText(category)) {
54
+ predicates.add(criteriaBuilder.equal(root.get("labelCategory"), category));
55
+ }
56
+ if (StringUtils.hasText(nameContains)) {
57
+ predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("labelName")),
58
+ "%" + nameContains.toLowerCase() + "%"));
59
+ }
60
+ return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
61
+ };
62
+ return labelRepository.findAll(spec, pageable).map(labelTaxonomyMapper::toTaxonomyLabelDTO);
63
+ }
64
+
65
+ @Override
66
+ @Transactional(readOnly = true)
67
+ public TaxonomyLabelDTO getLabelByIdOrName(String idOrName) {
68
+ Label label;
69
+ try {
70
+ UUID labelId = UUID.fromString(idOrName);
71
+ label = labelRepository.findById(labelId)
72
+ .orElseThrow(() -> new ResourceNotFoundException("Label", "id", idOrName));
73
+ } catch (IllegalArgumentException ex) {
74
+ // Not a UUID, try by name
75
+ label = labelRepository.findByLabelName(idOrName)
76
+ .orElseThrow(() -> new ResourceNotFoundException("Label", "name", idOrName));
77
+ }
78
+ return labelTaxonomyMapper.toTaxonomyLabelDTO(label);
79
+ }
80
+
81
+ @Override
82
+ public TaxonomyLabelDTO createLabel(TaxonomyLabelDTO labelDTO, UUID creatorUserId) {
83
+ if (!StringUtils.hasText(labelDTO.getLabelName())){
84
+ throw new IllegalArgumentException("Label name cannot be empty.");
85
+ }
86
+ labelRepository.findByLabelName(labelDTO.getLabelName()).ifPresent(l -> {
87
+ throw new ConflictException("Label with name '" + labelDTO.getLabelName() + "' already exists.");
88
+ });
89
+
90
+ Label label = labelTaxonomyMapper.toLabelEntity(labelDTO);
91
+ label.setCreatedByUserId(creatorUserId);
92
+ // ID, createdAt, updatedAt are handled by entity constructor and @PrePersist/@PreUpdate
93
+
94
+ Label savedLabel = labelRepository.save(label);
95
+ return labelTaxonomyMapper.toTaxonomyLabelDTO(savedLabel);
96
+ }
97
+
98
+ @Override
99
+ public TaxonomyLabelDTO updateLabel(String idOrName, TaxonomyLabelDTO labelDTO, UUID updaterUserId) {
100
+ final Label existingLabel = findLabelByIdOrName(idOrName);
101
+
102
+ // Check for name conflict if name is being changed
103
+ if (StringUtils.hasText(labelDTO.getLabelName()) && !existingLabel.getLabelName().equals(labelDTO.getLabelName())) {
104
+ Optional<Label> conflictingLabel = labelRepository.findByLabelName(labelDTO.getLabelName());
105
+ if (conflictingLabel.isPresent() && !conflictingLabel.get().getLabelId().equals(existingLabel.getLabelId())) {
106
+ throw new ConflictException("Another label with name '" + labelDTO.getLabelName() + "' already exists.");
107
+ }
108
+ }
109
+
110
+ labelTaxonomyMapper.updateLabelEntityFromDto(existingLabel, labelDTO);
111
+ // existingLabel.setUpdatedByUserId(updaterUserId); // If tracking updater
112
+ Label updatedLabel = labelRepository.save(existingLabel); // @PreUpdate will set updatedAt
113
+ return labelTaxonomyMapper.toTaxonomyLabelDTO(updatedLabel);
114
+ }
115
+
116
+ @Override
117
+ public void deleteLabel(String idOrName) {
118
+ Label labelToDelete = findLabelByIdOrName(idOrName);
119
+
120
+ // Check if the label is used in any assignments
121
+ long usageCount = assetLabelAssignmentRepository.countByLabel(labelToDelete);
122
+ if (usageCount > 0) {
123
+ throw new ConflictException("Label '" + labelToDelete.getLabelName() + "' cannot be deleted as it is currently assigned to " + usageCount + " asset(s).");
124
+ }
125
+
126
+ try {
127
+ labelRepository.delete(labelToDelete);
128
+ log.info("Deleted label: {}", labelToDelete.getLabelName());
129
+ } catch (DataIntegrityViolationException e) {
130
+ // This is a fallback, primary check is done by usageCount
131
+ throw new ConflictException("Label '" + labelToDelete.getLabelName() + "' cannot be deleted due to existing references (e.g., assignments).");
132
+ }
133
+ }
134
+
135
+ private Label findLabelByIdOrName(String idOrName) {
136
+ try {
137
+ UUID labelId = UUID.fromString(idOrName);
138
+ return labelRepository.findById(labelId)
139
+ .orElseThrow(() -> new ResourceNotFoundException("Label", "id", idOrName));
140
+ } catch (IllegalArgumentException ex) {
141
+ // Not a UUID, try by name
142
+ return labelRepository.findByLabelName(idOrName)
143
+ .orElseThrow(() -> new ResourceNotFoundException("Label", "name", idOrName));
144
+ }
145
+ }
146
+ }
src/main/java/com/dalab/catalog/web/rest/AssetController.java ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.web.rest;
2
+
3
+ import java.net.URI;
4
+ import java.util.List;
5
+ import java.util.Map;
6
+ import java.util.UUID;
7
+
8
+ import org.slf4j.Logger;
9
+ import org.slf4j.LoggerFactory;
10
+ import org.springframework.beans.factory.annotation.Autowired;
11
+ import org.springframework.data.domain.Page;
12
+ import org.springframework.data.domain.Pageable;
13
+ import org.springframework.data.web.PageableDefault;
14
+ import org.springframework.http.ResponseEntity;
15
+ import org.springframework.security.access.prepost.PreAuthorize;
16
+ import org.springframework.web.bind.annotation.DeleteMapping;
17
+ import org.springframework.web.bind.annotation.GetMapping;
18
+ import org.springframework.web.bind.annotation.PathVariable;
19
+ import org.springframework.web.bind.annotation.PostMapping;
20
+ import org.springframework.web.bind.annotation.PutMapping;
21
+ import org.springframework.web.bind.annotation.RequestBody;
22
+ import org.springframework.web.bind.annotation.RequestMapping;
23
+ import org.springframework.web.bind.annotation.RequestParam;
24
+ import org.springframework.web.bind.annotation.RestController;
25
+ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
26
+
27
+ import com.dalab.catalog.dto.AssetComplianceStatusDTO;
28
+ import com.dalab.catalog.dto.AssetInputDTO;
29
+ import com.dalab.catalog.dto.AssetLabelsPostRequestDTO;
30
+ import com.dalab.catalog.dto.AssetLabelsResponseDTO;
31
+ import com.dalab.catalog.dto.AssetOutputDTO;
32
+ import com.dalab.catalog.dto.AssetPolicyMappingDTO;
33
+ import com.dalab.catalog.dto.AssetSchemaDTO;
34
+ import com.dalab.catalog.dto.AssetUsageAnalyticsDTO;
35
+ import com.dalab.catalog.dto.BusinessMetadataInputDTO;
36
+ import com.dalab.catalog.dto.BusinessMetadataResponseDTO;
37
+ import com.dalab.catalog.dto.CatalogFiltersDTO;
38
+ import com.dalab.catalog.dto.EnhancedLineageDTO;
39
+ import com.dalab.catalog.dto.LabelOutputDTO;
40
+ import com.dalab.catalog.dto.LineageResponseDTO;
41
+ import com.dalab.catalog.security.SecurityUtils;
42
+ import com.dalab.catalog.service.IAssetService;
43
+
44
+ import io.swagger.v3.oas.annotations.Operation;
45
+ import io.swagger.v3.oas.annotations.Parameter;
46
+ import io.swagger.v3.oas.annotations.media.Content;
47
+ import io.swagger.v3.oas.annotations.media.ExampleObject;
48
+ import io.swagger.v3.oas.annotations.media.Schema;
49
+ import io.swagger.v3.oas.annotations.responses.ApiResponse;
50
+ import io.swagger.v3.oas.annotations.responses.ApiResponses;
51
+ import jakarta.validation.Valid;
52
+
53
+ @RestController
54
+ @RequestMapping("/api/v1/catalog") // Base path for catalog service
55
+ public class AssetController {
56
+
57
+ private static final Logger log = LoggerFactory.getLogger(AssetController.class);
58
+
59
+ private final IAssetService assetService;
60
+
61
+ @Autowired
62
+ public AssetController(IAssetService assetService) {
63
+ this.assetService = assetService;
64
+ }
65
+
66
+ @GetMapping("/assets")
67
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") // Adjust roles as needed
68
+ public ResponseEntity<Page<AssetOutputDTO>> getAllAssets(
69
+ @PageableDefault(size = 20, sort = "assetName") Pageable pageable,
70
+ @RequestParam(required = false) String cloudProvider,
71
+ @RequestParam(required = false) String assetType,
72
+ @RequestParam(required = false) String region,
73
+ @RequestParam(required = false) List<String> labels,
74
+ @RequestParam(required = false) String nameContains,
75
+ @RequestParam(required = false) UUID connectionId) {
76
+ log.info("REST request to get all Assets with filters: provider={}, type={}, region={}, labels={}, name={}, connectionId={}",
77
+ cloudProvider, assetType, region, labels, nameContains, connectionId);
78
+ Page<AssetOutputDTO> assetPage = assetService.getAllAssets(pageable, cloudProvider, assetType, region, labels, nameContains, connectionId);
79
+ return ResponseEntity.ok(assetPage);
80
+ }
81
+
82
+ @GetMapping("/assets/{assetId}")
83
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") // Adjust roles
84
+ public ResponseEntity<AssetOutputDTO> getAssetById(@PathVariable UUID assetId) {
85
+ log.info("REST request to get Asset by id: {}", assetId);
86
+ AssetOutputDTO assetDTO = assetService.getAssetById(assetId);
87
+ return ResponseEntity.ok(assetDTO);
88
+ }
89
+
90
+ @PostMapping("/assets")
91
+ @PreAuthorize("hasAuthority('ROLE_ADMIN') or @systemSecurityService.isSystemUser(authentication)") // Example: Admin or System
92
+ public ResponseEntity<AssetOutputDTO> createAsset(@Valid @RequestBody AssetInputDTO assetInputDTO) {
93
+ log.info("REST request to create Asset: {}", assetInputDTO.getAssetName());
94
+ UUID creatorUserId = SecurityUtils.getAuthenticatedUserId();
95
+
96
+ AssetOutputDTO createdAsset = assetService.createAsset(assetInputDTO, creatorUserId);
97
+
98
+ URI location = ServletUriComponentsBuilder.fromCurrentRequest()
99
+ .path("/{id}")
100
+ .buildAndExpand(createdAsset.getAssetId())
101
+ .toUri();
102
+
103
+ return ResponseEntity.created(location).body(createdAsset);
104
+ }
105
+
106
+ @PutMapping("/assets/{assetId}/metadata/business")
107
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD')") // Example roles, adjust as needed
108
+ public ResponseEntity<AssetOutputDTO> updateBusinessMetadata(
109
+ @PathVariable UUID assetId,
110
+ @RequestBody Map<String, Object> businessMetadata) {
111
+ log.info("REST request to update business metadata for Asset ID: {}", assetId);
112
+ UUID updaterUserId = SecurityUtils.getAuthenticatedUserId();
113
+
114
+ AssetOutputDTO updatedAsset = assetService.updateBusinessMetadata(assetId, businessMetadata, updaterUserId);
115
+ return ResponseEntity.ok(updatedAsset);
116
+ }
117
+
118
+ @PutMapping("/assets/{assetId}/business-context")
119
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_POLICY_MANAGER')")
120
+ @Operation(
121
+ summary = "Update enhanced business context and metadata",
122
+ description = "Update comprehensive business metadata including steward relationships, compliance classification, and business context",
123
+ tags = {"Assets", "Business Metadata"}
124
+ )
125
+ @ApiResponses(value = {
126
+ @ApiResponse(responseCode = "200", description = "Business context updated successfully"),
127
+ @ApiResponse(responseCode = "400", description = "Invalid business metadata input"),
128
+ @ApiResponse(responseCode = "401", description = "Unauthorized"),
129
+ @ApiResponse(responseCode = "403", description = "Forbidden - insufficient permissions"),
130
+ @ApiResponse(responseCode = "404", description = "Asset not found"),
131
+ @ApiResponse(responseCode = "500", description = "Internal server error")
132
+ })
133
+ public ResponseEntity<BusinessMetadataResponseDTO> updateBusinessContext(
134
+ @Parameter(description = "Asset ID", required = true) @PathVariable UUID assetId,
135
+ @Parameter(description = "Enhanced business metadata with steward relationships and compliance classification", required = true)
136
+ @Valid @RequestBody BusinessMetadataInputDTO businessMetadataInput) {
137
+ log.info("REST request to update enhanced business context for Asset ID: {}", assetId);
138
+ UUID updaterUserId = SecurityUtils.getAuthenticatedUserId();
139
+
140
+ BusinessMetadataResponseDTO response = assetService.updateBusinessContext(assetId, businessMetadataInput, updaterUserId);
141
+ return ResponseEntity.ok(response);
142
+ }
143
+
144
+ @GetMapping("/assets/{assetId}/business-metadata")
145
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_POLICY_MANAGER', 'ROLE_USER')")
146
+ @Operation(
147
+ summary = "Get comprehensive business metadata",
148
+ description = "Retrieve complete business metadata including business context, steward relationships, compliance classification, audit information, and validation status",
149
+ tags = {"Assets", "Business Metadata"}
150
+ )
151
+ @ApiResponses(value = {
152
+ @ApiResponse(responseCode = "200", description = "Business metadata retrieved successfully"),
153
+ @ApiResponse(responseCode = "401", description = "Unauthorized"),
154
+ @ApiResponse(responseCode = "403", description = "Forbidden - insufficient permissions"),
155
+ @ApiResponse(responseCode = "404", description = "Asset not found"),
156
+ @ApiResponse(responseCode = "500", description = "Internal server error")
157
+ })
158
+ public ResponseEntity<BusinessMetadataResponseDTO> getBusinessMetadata(
159
+ @Parameter(description = "Asset ID", required = true) @PathVariable UUID assetId) {
160
+ log.info("REST request to get business metadata for Asset ID: {}", assetId);
161
+
162
+ BusinessMetadataResponseDTO response = assetService.getBusinessMetadata(assetId);
163
+ return ResponseEntity.ok(response);
164
+ }
165
+
166
+ @GetMapping("/assets/{assetId}/lineage")
167
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") // Adjust roles as needed
168
+ public ResponseEntity<LineageResponseDTO> getAssetLineage(@PathVariable UUID assetId) {
169
+ log.info("REST request to get lineage for Asset ID: {}", assetId);
170
+ LineageResponseDTO lineageResponse = assetService.getAssetLineage(assetId);
171
+ return ResponseEntity.ok(lineageResponse);
172
+ }
173
+
174
+ @GetMapping("/assets/{assetId}/labels")
175
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") // Adjust roles
176
+ public ResponseEntity<AssetLabelsResponseDTO> getAssetLabels(@PathVariable UUID assetId) {
177
+ log.info("REST request to get labels for Asset ID: {}", assetId);
178
+ List<LabelOutputDTO> labels = assetService.getAssetLabels(assetId);
179
+ return ResponseEntity.ok(new AssetLabelsResponseDTO(labels));
180
+ }
181
+
182
+ @PostMapping("/assets/{assetId}/labels")
183
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") // Or more specific roles like Data Steward
184
+ public ResponseEntity<AssetLabelsResponseDTO> assignLabelsToAsset(
185
+ @PathVariable UUID assetId,
186
+ @Valid @RequestBody AssetLabelsPostRequestDTO labelsToAssignRequest) {
187
+ log.info("REST request to assign labels to Asset ID: {}", assetId);
188
+ UUID assignerUserId = SecurityUtils.getAuthenticatedUserId();
189
+
190
+ List<LabelOutputDTO> updatedLabels = assetService.assignLabelsToAsset(assetId, labelsToAssignRequest, assignerUserId);
191
+ return ResponseEntity.ok(new AssetLabelsResponseDTO(updatedLabels));
192
+ }
193
+
194
+ @DeleteMapping("/assets/{assetId}/labels/{labelName}")
195
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") // Or more specific roles
196
+ public ResponseEntity<Void> removeLabelFromAsset(
197
+ @PathVariable UUID assetId,
198
+ @PathVariable String labelName) {
199
+ log.info("REST request to remove label '{}' from Asset ID: {}", labelName, assetId);
200
+ UUID removerUserId = SecurityUtils.getAuthenticatedUserId();
201
+
202
+ assetService.removeLabelFromAsset(assetId, labelName, removerUserId);
203
+ return ResponseEntity.noContent().build();
204
+ }
205
+
206
+ @GetMapping("/assets/{assetId}/usage")
207
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_USER')")
208
+ @Operation(
209
+ summary = "Get asset usage analytics",
210
+ description = "Retrieve comprehensive usage analytics for a specific asset including access patterns, user behavior, performance metrics, and optimization recommendations",
211
+ tags = {"Asset Management"},
212
+ responses = {
213
+ @ApiResponse(responseCode = "200", description = "Usage analytics retrieved successfully"),
214
+ @ApiResponse(responseCode = "404", description = "Asset not found"),
215
+ @ApiResponse(responseCode = "403", description = "Access denied")
216
+ }
217
+ )
218
+ public ResponseEntity<AssetUsageAnalyticsDTO> getAssetUsageAnalytics(
219
+ @Parameter(description = "Asset ID", required = true) @PathVariable UUID assetId) {
220
+
221
+ AssetUsageAnalyticsDTO analytics = assetService.getAssetUsageAnalytics(assetId);
222
+ return ResponseEntity.ok(analytics);
223
+ }
224
+
225
+ @GetMapping("/assets/{assetId}/schema")
226
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_DATA_ENGINEER', 'ROLE_USER')")
227
+ @Operation(
228
+ summary = "Get comprehensive asset schema information",
229
+ description = "Retrieve detailed schema structure including field metadata, data types, constraints, indexes, statistics, lineage, and evolution history. This Priority 2 endpoint provides enhanced catalog functionality for complete asset understanding.",
230
+ tags = {"Asset Management", "Schema"},
231
+ responses = {
232
+ @ApiResponse(
233
+ responseCode = "200",
234
+ description = "Asset schema information retrieved successfully",
235
+ content = @Content(
236
+ mediaType = "application/json",
237
+ schema = @Schema(implementation = AssetSchemaDTO.class),
238
+ examples = @ExampleObject(
239
+ name = "Customer Table Schema",
240
+ description = "Example schema for a customer table with comprehensive metadata",
241
+ value = """
242
+ {
243
+ "assetId": "123e4567-e89b-12d3-a456-426614174000",
244
+ "assetName": "customer_master_table",
245
+ "schemaVersion": "2.1.0",
246
+ "schemaType": {
247
+ "type": "TABLE",
248
+ "format": "PARQUET",
249
+ "encoding": "UTF-8"
250
+ },
251
+ "fields": [
252
+ {
253
+ "fieldName": "customer_id",
254
+ "dataType": "BIGINT",
255
+ "logicalType": "IDENTIFIER",
256
+ "nullable": false,
257
+ "primaryKey": true,
258
+ "description": "Unique identifier for customer",
259
+ "classification": "PII",
260
+ "statistics": {
261
+ "distinctCount": 125000,
262
+ "fillRate": 100.0
263
+ }
264
+ }
265
+ ],
266
+ "statistics": {
267
+ "totalRows": 125000,
268
+ "totalColumns": 5,
269
+ "totalSize": 52428800,
270
+ "compressionRatio": 0.75
271
+ }
272
+ }
273
+ """
274
+ )
275
+ )
276
+ ),
277
+ @ApiResponse(responseCode = "404", description = "Asset not found"),
278
+ @ApiResponse(responseCode = "403", description = "Access denied - insufficient permissions"),
279
+ @ApiResponse(responseCode = "500", description = "Internal server error")
280
+ }
281
+ )
282
+ public ResponseEntity<AssetSchemaDTO> getAssetSchema(
283
+ @Parameter(
284
+ description = "Unique identifier of the asset to retrieve schema information for",
285
+ required = true,
286
+ example = "123e4567-e89b-12d3-a456-426614174000"
287
+ )
288
+ @PathVariable UUID assetId) {
289
+
290
+ AssetSchemaDTO schema = assetService.getAssetSchema(assetId);
291
+ return ResponseEntity.ok(schema);
292
+ }
293
+
294
+ @GetMapping("/assets/{assetId}/lineage/enhanced")
295
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_DATA_ENGINEER', 'ROLE_USER')")
296
+ @Operation(
297
+ summary = "Get enhanced data lineage visualization",
298
+ description = "Retrieve comprehensive lineage information with visualization metadata, graph nodes, edges, statistics, and critical path analysis. This Priority 2 endpoint provides detailed lineage for interactive graph rendering.",
299
+ tags = {"Asset Management", "Lineage"},
300
+ responses = {
301
+ @ApiResponse(responseCode = "200", description = "Enhanced lineage information retrieved successfully"),
302
+ @ApiResponse(responseCode = "404", description = "Asset not found"),
303
+ @ApiResponse(responseCode = "403", description = "Access denied"),
304
+ @ApiResponse(responseCode = "500", description = "Internal server error")
305
+ }
306
+ )
307
+ public ResponseEntity<EnhancedLineageDTO> getEnhancedLineage(
308
+ @Parameter(description = "Asset ID", required = true) @PathVariable UUID assetId,
309
+ @Parameter(description = "Maximum lineage depth to traverse", example = "5")
310
+ @RequestParam(required = false, defaultValue = "5") Integer maxDepth) {
311
+
312
+ EnhancedLineageDTO lineage = assetService.getEnhancedLineage(assetId, maxDepth);
313
+ return ResponseEntity.ok(lineage);
314
+ }
315
+
316
+ @GetMapping("/assets/{assetId}/policies")
317
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_POLICY_MANAGER', 'ROLE_USER')")
318
+ @Operation(
319
+ summary = "Get asset policy mapping",
320
+ description = "Retrieve information about which policies apply to an asset, their compliance status, risk assessment, and recommendations. This Priority 2 endpoint provides policy-asset relationship details.",
321
+ tags = {"Asset Management", "Policy"},
322
+ responses = {
323
+ @ApiResponse(responseCode = "200", description = "Asset policy mapping retrieved successfully"),
324
+ @ApiResponse(responseCode = "404", description = "Asset not found"),
325
+ @ApiResponse(responseCode = "403", description = "Access denied"),
326
+ @ApiResponse(responseCode = "500", description = "Internal server error")
327
+ }
328
+ )
329
+ public ResponseEntity<AssetPolicyMappingDTO> getAssetPolicyMapping(
330
+ @Parameter(description = "Asset ID", required = true) @PathVariable UUID assetId) {
331
+
332
+ AssetPolicyMappingDTO mapping = assetService.getAssetPolicyMapping(assetId);
333
+ return ResponseEntity.ok(mapping);
334
+ }
335
+
336
+ @GetMapping("/filters")
337
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_DATA_ENGINEER', 'ROLE_USER')")
338
+ @Operation(
339
+ summary = "Get catalog faceted search filters",
340
+ description = "Retrieve filter metadata for advanced search and filtering in the catalog UI. This Priority 2 endpoint provides filter categories, options, quick filters, and search capabilities.",
341
+ tags = {"Catalog", "Search"},
342
+ responses = {
343
+ @ApiResponse(responseCode = "200", description = "Catalog filters retrieved successfully"),
344
+ @ApiResponse(responseCode = "403", description = "Access denied"),
345
+ @ApiResponse(responseCode = "500", description = "Internal server error")
346
+ }
347
+ )
348
+ public ResponseEntity<CatalogFiltersDTO> getCatalogFilters() {
349
+
350
+ CatalogFiltersDTO filters = assetService.getCatalogFilters();
351
+ return ResponseEntity.ok(filters);
352
+ }
353
+
354
+ @GetMapping("/assets/{assetId}/compliance")
355
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DATA_STEWARD', 'ROLE_POLICY_MANAGER', 'ROLE_COMPLIANCE_OFFICER')")
356
+ @Operation(
357
+ summary = "Get asset compliance status",
358
+ description = "Retrieve detailed compliance status including framework compliance, violations, controls, risk assessment, and recommendations. This Priority 2 endpoint provides comprehensive compliance monitoring.",
359
+ tags = {"Asset Management", "Compliance"},
360
+ responses = {
361
+ @ApiResponse(responseCode = "200", description = "Asset compliance status retrieved successfully"),
362
+ @ApiResponse(responseCode = "404", description = "Asset not found"),
363
+ @ApiResponse(responseCode = "403", description = "Access denied"),
364
+ @ApiResponse(responseCode = "500", description = "Internal server error")
365
+ }
366
+ )
367
+ public ResponseEntity<AssetComplianceStatusDTO> getAssetComplianceStatus(
368
+ @Parameter(description = "Asset ID", required = true) @PathVariable UUID assetId) {
369
+
370
+ AssetComplianceStatusDTO compliance = assetService.getAssetComplianceStatus(assetId);
371
+ return ResponseEntity.ok(compliance);
372
+ }
373
+
374
+ // TODO: Implement other Asset related endpoints (Taxonomy APIs)
375
+ }
src/main/java/com/dalab/catalog/web/rest/LabelTaxonomyController.java ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.web.rest;
2
+
3
+ import com.dalab.catalog.dto.TaxonomyLabelDTO;
4
+ import com.dalab.catalog.dto.TaxonomyLabelsResponseDTO;
5
+ import com.dalab.catalog.service.ILabelTaxonomyService;
6
+ import com.dalab.catalog.security.SecurityUtils;
7
+ import jakarta.validation.Valid;
8
+ import org.slf4j.Logger;
9
+ import org.slf4j.LoggerFactory;
10
+ import org.springframework.beans.factory.annotation.Autowired;
11
+ import org.springframework.data.domain.Page;
12
+ import org.springframework.data.domain.Pageable;
13
+ import org.springframework.data.web.PageableDefault;
14
+ import org.springframework.http.ResponseEntity;
15
+ import org.springframework.security.access.prepost.PreAuthorize;
16
+ import org.springframework.web.bind.annotation.*;
17
+ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
18
+
19
+ import java.net.URI;
20
+ import java.util.UUID;
21
+
22
+ @RestController
23
+ @RequestMapping("/api/v1/catalog/taxonomy")
24
+ public class LabelTaxonomyController {
25
+
26
+ private static final Logger log = LoggerFactory.getLogger(LabelTaxonomyController.class);
27
+
28
+ private final ILabelTaxonomyService labelTaxonomyService;
29
+
30
+ @Autowired
31
+ public LabelTaxonomyController(ILabelTaxonomyService labelTaxonomyService) {
32
+ this.labelTaxonomyService = labelTaxonomyService;
33
+ }
34
+
35
+ @GetMapping("/labels")
36
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") // Adjust as needed
37
+ public ResponseEntity<TaxonomyLabelsResponseDTO> getAllLabels(
38
+ @PageableDefault(size = 100, sort = "labelName") Pageable pageable,
39
+ @RequestParam(required = false) String category,
40
+ @RequestParam(required = false) String nameContains) {
41
+ log.info("REST request to get all taxonomy labels with filters: category={}, nameContains={}", category, nameContains);
42
+ Page<TaxonomyLabelDTO> labelPage = labelTaxonomyService.getAllLabels(pageable, category, nameContains);
43
+ return ResponseEntity.ok(new TaxonomyLabelsResponseDTO(labelPage.getContent()));
44
+ }
45
+
46
+ @PostMapping("/labels")
47
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')")
48
+ public ResponseEntity<TaxonomyLabelDTO> createLabel(@Valid @RequestBody TaxonomyLabelDTO labelDTO) {
49
+ log.info("REST request to create taxonomy label: {}", labelDTO.getLabelName());
50
+ UUID creatorUserId = SecurityUtils.getAuthenticatedUserId();
51
+ TaxonomyLabelDTO createdLabel = labelTaxonomyService.createLabel(labelDTO, creatorUserId);
52
+ URI location = ServletUriComponentsBuilder.fromCurrentRequest()
53
+ .path("/{idOrName}")
54
+ .buildAndExpand(createdLabel.getLabelId())
55
+ .toUri();
56
+ return ResponseEntity.created(location).body(createdLabel);
57
+ }
58
+
59
+ @GetMapping("/labels/{idOrName}")
60
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')")
61
+ public ResponseEntity<TaxonomyLabelDTO> getLabelByIdOrName(@PathVariable String idOrName) {
62
+ log.info("REST request to get taxonomy label by id/name: {}", idOrName);
63
+ TaxonomyLabelDTO labelDTO = labelTaxonomyService.getLabelByIdOrName(idOrName);
64
+ return ResponseEntity.ok(labelDTO);
65
+ }
66
+
67
+ @PutMapping("/labels/{idOrName}")
68
+ @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')")
69
+ public ResponseEntity<TaxonomyLabelDTO> updateLabel(
70
+ @PathVariable String idOrName,
71
+ @Valid @RequestBody TaxonomyLabelDTO labelDTO) {
72
+ log.info("REST request to update taxonomy label '{}': {}", idOrName, labelDTO.getLabelName());
73
+ UUID updaterUserId = SecurityUtils.getAuthenticatedUserId();
74
+ TaxonomyLabelDTO updatedLabel = labelTaxonomyService.updateLabel(idOrName, labelDTO, updaterUserId);
75
+ return ResponseEntity.ok(updatedLabel);
76
+ }
77
+
78
+ @DeleteMapping("/labels/{idOrName}")
79
+ @PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_POLICY_MANAGER')")
80
+ public ResponseEntity<Void> deleteLabel(@PathVariable String idOrName) {
81
+ log.info("REST request to delete taxonomy label by id/name: {}", idOrName);
82
+ labelTaxonomyService.deleteLabel(idOrName);
83
+ return ResponseEntity.noContent().build();
84
+ }
85
+ }
src/main/resources/application.properties ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DALab Da-catalog Service Configuration
2
+ # Standardized configuration for Docker and local development
3
+
4
+ spring.application.name=da-catalog
5
+
6
+ # Server Configuration
7
+ server.port=8080
8
+ server.servlet.context-path=/api/v1/catalog
9
+
10
+ # Database Configuration - using infrastructure PostgreSQL
11
+ spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/da_catalog
12
+ spring.datasource.username=${DB_USER:da_catalog_user}
13
+ spring.datasource.password=${DB_PASS:da_catalog_pass}
14
+ spring.datasource.driver-class-name=org.postgresql.Driver
15
+
16
+ # Connection Pool Configuration
17
+ spring.datasource.hikari.maximum-pool-size=${DB_POOL_SIZE:10}
18
+ spring.datasource.hikari.minimum-idle=2
19
+ spring.datasource.hikari.connection-timeout=30000
20
+ spring.datasource.hikari.idle-timeout=600000
21
+ spring.datasource.hikari.max-lifetime=1800000
22
+
23
+ # JPA Configuration
24
+ spring.jpa.hibernate.ddl-auto=${JPA_DDL_AUTO:update}
25
+ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
26
+ spring.jpa.properties.hibernate.default_schema=da_catalog_schema
27
+ spring.jpa.properties.hibernate.jdbc.time_zone=UTC
28
+ spring.jpa.properties.hibernate.format_sql=true
29
+ spring.jpa.show-sql=${JPA_SHOW_SQL:false}
30
+
31
+ # Kafka Configuration - using infrastructure Kafka
32
+ spring.kafka.bootstrap-servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
33
+ spring.kafka.consumer.group-id=${spring.application.name}
34
+ spring.kafka.consumer.auto-offset-reset=earliest
35
+ spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
36
+ spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
37
+ spring.kafka.consumer.properties.spring.json.trusted.packages=com.dalab.*
38
+ spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
39
+ spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
40
+ spring.kafka.producer.retries=3
41
+ spring.kafka.producer.acks=all
42
+
43
+ # Security Configuration - using infrastructure Keycloak
44
+ spring.security.oauth2.resourceserver.jwt.issuer-uri=${KEYCLOAK_AUTH_SERVER_URL:http://localhost:8180}/realms/dalab
45
+ spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${KEYCLOAK_AUTH_SERVER_URL:http://localhost:8180}/realms/dalab/protocol/openid-connect/certs
46
+
47
+ # Redis Configuration
48
+ spring.data.redis.host=${REDIS_HOST:localhost}
49
+ spring.data.redis.port=${REDIS_PORT:6379}
50
+ spring.data.redis.password=${REDIS_PASS:}
51
+ spring.data.redis.timeout=2000ms
52
+
53
+ # Management/Actuator Configuration
54
+ management.endpoints.web.exposure.include=health,info,metrics,prometheus
55
+ management.endpoint.health.show-details=when-authorized
56
+ management.metrics.export.prometheus.enabled=true
57
+ management.endpoint.health.probes.enabled=true
58
+
59
+ # Logging Configuration
60
+ logging.level.com.dalab=${LOG_LEVEL:INFO}
61
+ logging.level.org.springframework.kafka=INFO
62
+ logging.level.org.springframework.security=INFO
63
+ logging.level.org.hibernate.SQL=${SQL_LOG_LEVEL:WARN}
64
+ logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n
65
+
66
+ # DALab Specific Configuration
67
+ dalab.service.name=Da-catalog Service
68
+ dalab.service.version=1.0.0
69
+ dalab.service.description=Da-catalog microservice for DALab platform
70
+
71
+ # Common entities database connection (for da-protos entities)
72
+ dalab.common-db.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/dalab_common
73
+ dalab.common-db.username=${DB_USER:dalab}
74
+ dalab.common-db.password=${DB_PASS:dalab}
75
+
76
+ # Kafka topics
77
+ dalab.kafka.topics.asset-changes=dalab.assets.changes
78
+ dalab.kafka.topics.asset-discovered=dalab.assets.discovered
79
+ dalab.kafka.topics.asset-labeled=dalab.assets.labeled
80
+ dalab.kafka.topics.policy-evaluations=dalab.policies.evaluations
81
+
82
+ # CORS Configuration
83
+ dalab.security.allowed-origins=${CORS_ORIGINS:http://localhost:3000,http://localhost:4200}
84
+
85
+ # JWT Claims Configuration
86
+ dalab.security.jwt.claims.user-id=sub
87
+ dalab.security.jwt.claims.username=preferred_username
88
+ dalab.security.jwt.claims.roles=realm_access.roles
89
+
90
+ # Profile specific overrides
91
+ spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}
src/test/java/com/dalab/catalog/controller/CatalogControllerTest.java ADDED
@@ -0,0 +1,430 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.catalog.controller;
2
+
3
+ import static org.mockito.ArgumentMatchers.*;
4
+ import static org.mockito.Mockito.*;
5
+ import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
6
+ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
7
+ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
8
+ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
9
+
10
+ import java.time.LocalDateTime;
11
+ import java.util.*;
12
+
13
+ import org.junit.jupiter.api.BeforeEach;
14
+ import org.junit.jupiter.api.Test;
15
+ import org.junit.jupiter.api.extension.ExtendWith;
16
+ import org.mockito.InjectMocks;
17
+ import org.mockito.Mock;
18
+ import org.mockito.junit.jupiter.MockitoExtension;
19
+ import org.springframework.beans.factory.annotation.Autowired;
20
+ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
21
+ import org.springframework.boot.test.mock.mockito.MockBean;
22
+ import org.springframework.data.domain.Page;
23
+ import org.springframework.data.domain.PageImpl;
24
+ import org.springframework.data.domain.PageRequest;
25
+ import org.springframework.data.domain.Pageable;
26
+ import org.springframework.http.MediaType;
27
+ import org.springframework.security.test.context.support.WithMockUser;
28
+ import org.springframework.test.context.ActiveProfiles;
29
+ import org.springframework.test.web.servlet.MockMvc;
30
+
31
+ import com.dalab.catalog.client.rest.dto.AssetCreateRequest;
32
+ import com.dalab.catalog.client.rest.dto.AssetDetail;
33
+ import com.dalab.catalog.client.rest.dto.AssetSearchRequest;
34
+ import com.dalab.catalog.client.rest.dto.AssetSummary;
35
+ import com.dalab.catalog.client.rest.dto.BusinessMetadataRequest;
36
+ import com.dalab.catalog.client.rest.dto.LabelAssignmentRequest;
37
+ import com.dalab.catalog.client.rest.dto.LineageDetail;
38
+ import com.dalab.catalog.client.rest.dto.UsageAnalytics;
39
+ import com.dalab.catalog.common.model.Asset;
40
+ import com.dalab.catalog.common.model.BusinessMetadata;
41
+ import com.dalab.catalog.common.model.Label;
42
+ import com.dalab.catalog.common.model.enums.CloudProvider;
43
+ import com.dalab.catalog.common.model.enums.ServiceType;
44
+ import com.dalab.catalog.service.ICatalogService;
45
+ import com.fasterxml.jackson.databind.ObjectMapper;
46
+
47
+ @WebMvcTest(CatalogController.class)
48
+ @ExtendWith(MockitoExtension.class)
49
+ @ActiveProfiles("test")
50
+ class CatalogControllerTest {
51
+
52
+ @Autowired
53
+ private MockMvc mockMvc;
54
+
55
+ @MockBean
56
+ private ICatalogService catalogService;
57
+
58
+ @Autowired
59
+ private ObjectMapper objectMapper;
60
+
61
+ private Asset testAsset;
62
+ private AssetSummary testAssetSummary;
63
+ private AssetDetail testAssetDetail;
64
+ private Label testLabel;
65
+ private BusinessMetadata testBusinessMetadata;
66
+
67
+ @BeforeEach
68
+ void setUp() {
69
+ // Create test asset
70
+ testAsset = new Asset();
71
+ testAsset.setId(1L);
72
+ testAsset.setAssetUri("gs://test-bucket/data.csv");
73
+ testAsset.setAssetName("Test Asset");
74
+ testAsset.setCloudProvider(CloudProvider.GCP);
75
+ testAsset.setServiceType(ServiceType.STORAGE);
76
+ testAsset.setStatus("ACTIVE");
77
+ testAsset.setDalabDiscoveredTs(LocalDateTime.now());
78
+ testAsset.setDalabLastScannedTs(LocalDateTime.now());
79
+
80
+ // Create test asset summary
81
+ testAssetSummary = AssetSummary.builder()
82
+ .assetId(1L)
83
+ .assetUri("gs://test-bucket/data.csv")
84
+ .assetName("Test Asset")
85
+ .cloudProvider("GCP")
86
+ .serviceType("STORAGE")
87
+ .status("ACTIVE")
88
+ .build();
89
+
90
+ // Create test asset detail
91
+ testAssetDetail = AssetDetail.builder()
92
+ .assetId(1L)
93
+ .assetUri("gs://test-bucket/data.csv")
94
+ .assetName("Test Asset")
95
+ .cloudProvider("GCP")
96
+ .serviceType("STORAGE")
97
+ .status("ACTIVE")
98
+ .discoveredTs(LocalDateTime.now())
99
+ .lastScannedTs(LocalDateTime.now())
100
+ .build();
101
+
102
+ // Create test label
103
+ testLabel = new Label();
104
+ testLabel.setId(1L);
105
+ testLabel.setLabelName("PII");
106
+ testLabel.setLabelCategory("COMPLIANCE");
107
+ testLabel.setDescription("Personally Identifiable Information");
108
+
109
+ // Create test business metadata
110
+ testBusinessMetadata = new BusinessMetadata();
111
+ testBusinessMetadata.setId(1L);
112
+ testBusinessMetadata.setAssetId(1L);
113
+ testBusinessMetadata.setBusinessOwner("John Doe");
114
+ testBusinessMetadata.setDataSteward("Jane Smith");
115
+ testBusinessMetadata.setDepartment("Engineering");
116
+ testBusinessMetadata.setBusinessCriticality("HIGH");
117
+ }
118
+
119
+ @Test
120
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
121
+ void createAsset_AsDataEngineer_ShouldSucceed() throws Exception {
122
+ // Given
123
+ AssetCreateRequest request = new AssetCreateRequest();
124
+ request.setAssetUri("gs://test-bucket/new-data.csv");
125
+ request.setAssetName("New Test Asset");
126
+ request.setCloudProvider("GCP");
127
+ request.setServiceType("STORAGE");
128
+
129
+ when(catalogService.createAsset(any(AssetCreateRequest.class))).thenReturn(testAssetDetail);
130
+
131
+ // When & Then
132
+ mockMvc.perform(post("/api/v1/catalog/assets")
133
+ .with(csrf())
134
+ .contentType(MediaType.APPLICATION_JSON)
135
+ .content(objectMapper.writeValueAsString(request)))
136
+ .andExpect(status().isCreated())
137
+ .andExpect(jsonPath("$.assetId").value(1))
138
+ .andExpect(jsonPath("$.assetUri").value("gs://test-bucket/data.csv"))
139
+ .andExpect(jsonPath("$.assetName").value("Test Asset"));
140
+ }
141
+
142
+ @Test
143
+ @WithMockUser(authorities = "ROLE_USER")
144
+ void createAsset_AsUser_ShouldBeForbidden() throws Exception {
145
+ // Given
146
+ AssetCreateRequest request = new AssetCreateRequest();
147
+ request.setAssetUri("gs://test-bucket/new-data.csv");
148
+ request.setAssetName("New Test Asset");
149
+
150
+ // When & Then
151
+ mockMvc.perform(post("/api/v1/catalog/assets")
152
+ .with(csrf())
153
+ .contentType(MediaType.APPLICATION_JSON)
154
+ .content(objectMapper.writeValueAsString(request)))
155
+ .andExpect(status().isForbidden());
156
+ }
157
+
158
+ @Test
159
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
160
+ void getAllAssets_AsDataEngineer_ShouldSucceed() throws Exception {
161
+ // Given
162
+ List<AssetSummary> assetList = Arrays.asList(testAssetSummary);
163
+ Page<AssetSummary> assetPage = new PageImpl<>(assetList, PageRequest.of(0, 20), 1);
164
+
165
+ when(catalogService.getAllAssets(any(Pageable.class))).thenReturn(assetPage);
166
+
167
+ // When & Then
168
+ mockMvc.perform(get("/api/v1/catalog/assets")
169
+ .param("page", "0")
170
+ .param("size", "20"))
171
+ .andExpect(status().isOk())
172
+ .andExpect(jsonPath("$.content[0].assetId").value(1))
173
+ .andExpect(jsonPath("$.content[0].assetUri").value("gs://test-bucket/data.csv"))
174
+ .andExpect(jsonPath("$.totalElements").value(1));
175
+ }
176
+
177
+ @Test
178
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
179
+ void getAssetById_AsDataEngineer_ShouldSucceed() throws Exception {
180
+ // Given
181
+ when(catalogService.getAssetById(1L)).thenReturn(Optional.of(testAssetDetail));
182
+
183
+ // When & Then
184
+ mockMvc.perform(get("/api/v1/catalog/assets/{assetId}", 1))
185
+ .andExpect(status().isOk())
186
+ .andExpect(jsonPath("$.assetId").value(1))
187
+ .andExpect(jsonPath("$.assetUri").value("gs://test-bucket/data.csv"))
188
+ .andExpect(jsonPath("$.assetName").value("Test Asset"));
189
+ }
190
+
191
+ @Test
192
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
193
+ void getAssetById_AssetNotFound_ShouldReturnNotFound() throws Exception {
194
+ // Given
195
+ when(catalogService.getAssetById(999L)).thenReturn(Optional.empty());
196
+
197
+ // When & Then
198
+ mockMvc.perform(get("/api/v1/catalog/assets/{assetId}", 999))
199
+ .andExpect(status().isNotFound());
200
+ }
201
+
202
+ @Test
203
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
204
+ void searchAssets_AsDataEngineer_ShouldSucceed() throws Exception {
205
+ // Given
206
+ AssetSearchRequest searchRequest = new AssetSearchRequest();
207
+ searchRequest.setQuery("test");
208
+ searchRequest.setCloudProvider("GCP");
209
+ searchRequest.setServiceType("STORAGE");
210
+
211
+ List<AssetSummary> searchResults = Arrays.asList(testAssetSummary);
212
+ Page<AssetSummary> searchPage = new PageImpl<>(searchResults, PageRequest.of(0, 20), 1);
213
+
214
+ when(catalogService.searchAssets(any(AssetSearchRequest.class), any(Pageable.class)))
215
+ .thenReturn(searchPage);
216
+
217
+ // When & Then
218
+ mockMvc.perform(post("/api/v1/catalog/assets/search")
219
+ .with(csrf())
220
+ .contentType(MediaType.APPLICATION_JSON)
221
+ .content(objectMapper.writeValueAsString(searchRequest))
222
+ .param("page", "0")
223
+ .param("size", "20"))
224
+ .andExpect(status().isOk())
225
+ .andExpect(jsonPath("$.content[0].assetId").value(1))
226
+ .andExpect(jsonPath("$.totalElements").value(1));
227
+ }
228
+
229
+ @Test
230
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
231
+ void assignLabelToAsset_AsDataEngineer_ShouldSucceed() throws Exception {
232
+ // Given
233
+ LabelAssignmentRequest request = new LabelAssignmentRequest();
234
+ request.setLabelId(1L);
235
+ request.setConfidenceScore(0.95);
236
+ request.setSource("MANUAL");
237
+
238
+ when(catalogService.assignLabelToAsset(eq(1L), any(LabelAssignmentRequest.class)))
239
+ .thenReturn(testAssetDetail);
240
+
241
+ // When & Then
242
+ mockMvc.perform(post("/api/v1/catalog/assets/{assetId}/labels", 1)
243
+ .with(csrf())
244
+ .contentType(MediaType.APPLICATION_JSON)
245
+ .content(objectMapper.writeValueAsString(request)))
246
+ .andExpect(status().isOk())
247
+ .andExpect(jsonPath("$.assetId").value(1));
248
+ }
249
+
250
+ @Test
251
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
252
+ void getAssetLineage_AsDataEngineer_ShouldSucceed() throws Exception {
253
+ // Given
254
+ LineageDetail lineageDetail = LineageDetail.builder()
255
+ .assetId(1L)
256
+ .assetUri("gs://test-bucket/data.csv")
257
+ .assetName("Test Asset")
258
+ .upstreamAssets(Arrays.asList())
259
+ .downstreamAssets(Arrays.asList())
260
+ .build();
261
+
262
+ when(catalogService.getAssetLineage(1L)).thenReturn(Optional.of(lineageDetail));
263
+
264
+ // When & Then
265
+ mockMvc.perform(get("/api/v1/catalog/lineage/{assetId}", 1))
266
+ .andExpect(status().isOk())
267
+ .andExpect(jsonPath("$.assetId").value(1))
268
+ .andExpect(jsonPath("$.assetUri").value("gs://test-bucket/data.csv"));
269
+ }
270
+
271
+ @Test
272
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
273
+ void getAssetLineage_AssetNotFound_ShouldReturnNotFound() throws Exception {
274
+ // Given
275
+ when(catalogService.getAssetLineage(999L)).thenReturn(Optional.empty());
276
+
277
+ // When & Then
278
+ mockMvc.perform(get("/api/v1/catalog/lineage/{assetId}", 999))
279
+ .andExpect(status().isNotFound());
280
+ }
281
+
282
+ @Test
283
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
284
+ void updateBusinessMetadata_AsDataEngineer_ShouldSucceed() throws Exception {
285
+ // Given
286
+ BusinessMetadataRequest request = new BusinessMetadataRequest();
287
+ request.setBusinessOwner("John Doe");
288
+ request.setDataSteward("Jane Smith");
289
+ request.setDepartment("Engineering");
290
+ request.setBusinessCriticality("HIGH");
291
+
292
+ when(catalogService.updateBusinessMetadata(eq(1L), any(BusinessMetadataRequest.class)))
293
+ .thenReturn(testBusinessMetadata);
294
+
295
+ // When & Then
296
+ mockMvc.perform(put("/api/v1/catalog/assets/{assetId}/business-metadata", 1)
297
+ .with(csrf())
298
+ .contentType(MediaType.APPLICATION_JSON)
299
+ .content(objectMapper.writeValueAsString(request)))
300
+ .andExpect(status().isOk())
301
+ .andExpect(jsonPath("$.assetId").value(1))
302
+ .andExpect(jsonPath("$.businessOwner").value("John Doe"));
303
+ }
304
+
305
+ @Test
306
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
307
+ void getUsageAnalytics_AsDataEngineer_ShouldSucceed() throws Exception {
308
+ // Given
309
+ UsageAnalytics analytics = UsageAnalytics.builder()
310
+ .assetId(1L)
311
+ .assetUri("gs://test-bucket/data.csv")
312
+ .accessCount(150)
313
+ .lastAccessed(LocalDateTime.now())
314
+ .accessFrequency("HIGH")
315
+ .build();
316
+
317
+ when(catalogService.getUsageAnalytics(1L)).thenReturn(Optional.of(analytics));
318
+
319
+ // When & Then
320
+ mockMvc.perform(get("/api/v1/catalog/assets/{assetId}/usage-analytics", 1))
321
+ .andExpect(status().isOk())
322
+ .andExpect(jsonPath("$.assetId").value(1))
323
+ .andExpect(jsonPath("$.accessCount").value(150))
324
+ .andExpect(jsonPath("$.accessFrequency").value("HIGH"));
325
+ }
326
+
327
+ @Test
328
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
329
+ void getUsageAnalytics_AssetNotFound_ShouldReturnNotFound() throws Exception {
330
+ // Given
331
+ when(catalogService.getUsageAnalytics(999L)).thenReturn(Optional.empty());
332
+
333
+ // When & Then
334
+ mockMvc.perform(get("/api/v1/catalog/assets/{assetId}/usage-analytics", 999))
335
+ .andExpect(status().isNotFound());
336
+ }
337
+
338
+ @Test
339
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
340
+ void deleteAsset_AsDataEngineer_ShouldSucceed() throws Exception {
341
+ // Given
342
+ when(catalogService.deleteAsset(1L)).thenReturn(true);
343
+
344
+ // When & Then
345
+ mockMvc.perform(delete("/api/v1/catalog/assets/{assetId}", 1)
346
+ .with(csrf()))
347
+ .andExpect(status().isNoContent());
348
+ }
349
+
350
+ @Test
351
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
352
+ void deleteAsset_AssetNotFound_ShouldReturnNotFound() throws Exception {
353
+ // Given
354
+ when(catalogService.deleteAsset(999L)).thenReturn(false);
355
+
356
+ // When & Then
357
+ mockMvc.perform(delete("/api/v1/catalog/assets/{assetId}", 999)
358
+ .with(csrf()))
359
+ .andExpect(status().isNotFound());
360
+ }
361
+
362
+ @Test
363
+ @WithMockUser(authorities = "ROLE_USER")
364
+ void deleteAsset_AsUser_ShouldBeForbidden() throws Exception {
365
+ // When & Then
366
+ mockMvc.perform(delete("/api/v1/catalog/assets/{assetId}", 1)
367
+ .with(csrf()))
368
+ .andExpect(status().isForbidden());
369
+ }
370
+
371
+ @Test
372
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
373
+ void createAsset_InvalidRequest_ShouldReturnBadRequest() throws Exception {
374
+ // Given
375
+ AssetCreateRequest request = new AssetCreateRequest();
376
+ // Missing required fields
377
+
378
+ // When & Then
379
+ mockMvc.perform(post("/api/v1/catalog/assets")
380
+ .with(csrf())
381
+ .contentType(MediaType.APPLICATION_JSON)
382
+ .content(objectMapper.writeValueAsString(request)))
383
+ .andExpect(status().isBadRequest());
384
+ }
385
+
386
+ @Test
387
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
388
+ void searchAssets_InvalidSearchRequest_ShouldReturnBadRequest() throws Exception {
389
+ // Given
390
+ AssetSearchRequest searchRequest = new AssetSearchRequest();
391
+ searchRequest.setQuery(""); // Empty query
392
+
393
+ // When & Then
394
+ mockMvc.perform(post("/api/v1/catalog/assets/search")
395
+ .with(csrf())
396
+ .contentType(MediaType.APPLICATION_JSON)
397
+ .content(objectMapper.writeValueAsString(searchRequest)))
398
+ .andExpect(status().isBadRequest());
399
+ }
400
+
401
+ @Test
402
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
403
+ void assignLabelToAsset_InvalidLabelRequest_ShouldReturnBadRequest() throws Exception {
404
+ // Given
405
+ LabelAssignmentRequest request = new LabelAssignmentRequest();
406
+ // Missing required fields
407
+
408
+ // When & Then
409
+ mockMvc.perform(post("/api/v1/catalog/assets/{assetId}/labels", 1)
410
+ .with(csrf())
411
+ .contentType(MediaType.APPLICATION_JSON)
412
+ .content(objectMapper.writeValueAsString(request)))
413
+ .andExpect(status().isBadRequest());
414
+ }
415
+
416
+ @Test
417
+ @WithMockUser(authorities = "ROLE_DATA_ENGINEER")
418
+ void updateBusinessMetadata_InvalidRequest_ShouldReturnBadRequest() throws Exception {
419
+ // Given
420
+ BusinessMetadataRequest request = new BusinessMetadataRequest();
421
+ // Missing required fields
422
+
423
+ // When & Then
424
+ mockMvc.perform(put("/api/v1/catalog/assets/{assetId}/business-metadata", 1)
425
+ .with(csrf())
426
+ .contentType(MediaType.APPLICATION_JSON)
427
+ .content(objectMapper.writeValueAsString(request)))
428
+ .andExpect(status().isBadRequest());
429
+ }
430
+ }
src/test/resources/application-test.yml ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Test configuration for da-catalog integration tests
2
+ spring:
3
+ application:
4
+ name: da-catalog-test
5
+
6
+ config:
7
+ import: classpath:config/catalog-config.yml
8
+
9
+ # Database configuration (using H2 for tests)
10
+ datasource:
11
+ type: com.zaxxer.hikari.HikariDataSource
12
+ driver-class-name: org.h2.Driver
13
+ url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
14
+ username: sa
15
+ password: password
16
+ hikari:
17
+ auto-commit: false
18
+ maximum-pool-size: 10
19
+ minimum-idle: 2
20
+ connection-timeout: 30000
21
+ idle-timeout: 600000
22
+ max-lifetime: 1800000
23
+
24
+ jpa:
25
+ database-platform: org.hibernate.dialect.H2Dialect
26
+ hibernate:
27
+ ddl-auto: create-drop
28
+ show-sql: false
29
+ properties:
30
+ hibernate:
31
+ format_sql: false
32
+ jdbc:
33
+ lob:
34
+ non_contextual_creation: true
35
+ dialect: org.hibernate.dialect.H2Dialect
36
+
37
+ h2:
38
+ console:
39
+ enabled: false
40
+
41
+ # Disable Liquibase for tests
42
+ liquibase:
43
+ enabled: false
44
+
45
+ # Disable Docker for tests
46
+ docker:
47
+ compose:
48
+ enabled: false
49
+
50
+ # Task execution configuration for tests
51
+ task:
52
+ execution:
53
+ pool:
54
+ core-size: 2
55
+ max-size: 4
56
+ queue-capacity: 100
57
+ thread-name-prefix: test-task-
58
+
59
+ # Cache configuration for tests
60
+ cache:
61
+ type: simple
62
+
63
+ # Disable Docker for tests
64
+ testcontainers:
65
+ enabled: false
66
+
67
+ # Cloud provider configuration for tests
68
+ aws:
69
+ enabled: false
70
+ access-key: test-access-key
71
+ secret-key: test-secret-key
72
+ region: us-west-2
73
+ s3:
74
+ bucket-name: test-bucket
75
+ dynamodb:
76
+ table-name: test-table
77
+
78
+ azure:
79
+ enabled: false
80
+ client-id: test-dummy-client-id
81
+ client-secret: test-dummy-client-secret
82
+ tenant-id: test-dummy-tenant-id
83
+ subscription-id: "test-dummy-subscription-id"
84
+ resource-group-name: "test-dummy-rg"
85
+ region: "test-dummy-region"
86
+ storage:
87
+ account-name: "test-dummy-storage-account"
88
+ container-name: "test-dummy-storage-container"
89
+
90
+ gcp:
91
+ project:
92
+ id: test-project
93
+ storage:
94
+ bucket-name: test-bucket
95
+ bigquery:
96
+ dataset: test_dataset
97
+ project-id: test-project
98
+
99
+ # Application settings
100
+ application:
101
+ kafka:
102
+ enabled: true
103
+ bootstrap-servers: localhost:9092
104
+ producer:
105
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
106
+ value-serializer: org.apache.kafka.common.serialization.StringSerializer
107
+ consumer:
108
+ group-id: da-catalog-test
109
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
110
+ value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
111
+ auto-offset-reset: earliest
112
+ scheduler:
113
+ enabled: false
114
+ metrics:
115
+ enabled: false
116
+
117
+ # Catalog-specific configuration
118
+ catalog:
119
+ search:
120
+ max-results: 100
121
+ default-page-size: 20
122
+ labeling:
123
+ auto-labeling-enabled: true
124
+ confidence-threshold: 0.8
125
+ lineage:
126
+ max-depth: 5
127
+ enabled: true
128
+
129
+ # JHipster configuration
130
+ jhipster:
131
+ clientApp:
132
+ name: "daCatalog"
133
+ security:
134
+ authentication:
135
+ jwt:
136
+ secret: test-jwt-secret
137
+ base64-secret: test-secret-which-needs-to-be-at-least-512-bits-long-need-to-be-at-least-512-bits-long-really-long-ok
138
+ token-validity-in-seconds: 86400
139
+ audit-events:
140
+ retention-period: 30
141
+ logging:
142
+ use-json-format: false
143
+ cors:
144
+ allowed-origins: "*"
145
+ allowed-methods: "*"
146
+
147
+ # Test data configuration
148
+ test:
149
+ data:
150
+ enabled: true
151
+ users:
152
+ - email: "admin@test.com"
153
+ roles: ["ADMIN", "USER"]
154
+ department: "IT"
155
+ - email: "cdo@test.com"
156
+ roles: ["CDO", "USER"]
157
+ department: "Executive"
158
+ - email: "director@test.com"
159
+ roles: ["DIRECTOR", "USER"]
160
+ department: "Management"
161
+ - email: "engineer@test.com"
162
+ roles: ["DATA_ENGINEER", "USER"]
163
+ department: "Engineering"
164
+ assets:
165
+ - uri: "gs://test-bucket/data.csv"
166
+ cloud-provider: "GCP"
167
+ service-type: "STORAGE"
168
+ name: "Customer Data CSV"
169
+ - uri: "s3://test-bucket/analytics.json"
170
+ cloud-provider: "AWS"
171
+ service-type: "STORAGE"
172
+ name: "Analytics Data JSON"
173
+ - uri: "https://test.blob.core.windows.net/models/"
174
+ cloud-provider: "AZURE"
175
+ service-type: "STORAGE"
176
+ name: "ML Models Directory"
177
+ labels:
178
+ - name: "PII"
179
+ category: "COMPLIANCE"
180
+ description: "Personally Identifiable Information"
181
+ - name: "PHI"
182
+ category: "COMPLIANCE"
183
+ description: "Protected Health Information"
184
+ - name: "FINANCIAL"
185
+ category: "COMPLIANCE"
186
+ description: "Financial Data"
187
+ - name: "PUBLIC"
188
+ category: "CLASSIFICATION"
189
+ description: "Public Data"
190
+ - name: "INTERNAL"
191
+ category: "CLASSIFICATION"
192
+ description: "Internal Use Only"
193
+ - name: "CONFIDENTIAL"
194
+ category: "CLASSIFICATION"
195
+ description: "Confidential Data"
196
+
197
+ # Performance test configuration
198
+ performance:
199
+ test:
200
+ enabled: false
201
+ concurrent-users: 10
202
+ ramp-up-time: 30
203
+ test-duration: 300
204
+ target-response-time: 2000