Spaces:
Build error
Build error
| package com.dalab.policyengine.service; | |
| import java.time.Instant; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.UUID; | |
| import org.slf4j.Logger; | |
| import org.slf4j.LoggerFactory; | |
| import org.springframework.beans.factory.annotation.Autowired; | |
| import org.springframework.data.domain.Page; | |
| import org.springframework.data.domain.Pageable; | |
| import org.springframework.data.jpa.domain.Specification; | |
| import org.springframework.stereotype.Service; | |
| import org.springframework.transaction.annotation.Transactional; | |
| import org.springframework.util.StringUtils; | |
| import com.dalab.policyengine.common.ConflictException; | |
| import com.dalab.policyengine.common.ResourceNotFoundException; | |
| import com.dalab.policyengine.dto.PolicyDraftActionDTO; | |
| import com.dalab.policyengine.dto.PolicyDraftInputDTO; | |
| import com.dalab.policyengine.dto.PolicyDraftOutputDTO; | |
| import com.dalab.policyengine.dto.PolicyImpactRequestDTO; | |
| import com.dalab.policyengine.dto.PolicyImpactResponseDTO; | |
| // Add missing DTO imports | |
| import com.dalab.policyengine.dto.PolicyInputDTO; | |
| import com.dalab.policyengine.dto.PolicyOutputDTO; | |
| import com.dalab.policyengine.dto.PolicySummaryDTO; | |
| import com.dalab.policyengine.mapper.PolicyDraftMapper; | |
| // Add missing service imports | |
| import com.dalab.policyengine.mapper.PolicyMapper; | |
| // Add missing entity imports | |
| import com.dalab.policyengine.model.Policy; | |
| import com.dalab.policyengine.model.PolicyDraft; | |
| import com.dalab.policyengine.model.PolicyDraftStatus; | |
| import com.dalab.policyengine.model.PolicyStatus; | |
| import com.dalab.policyengine.repository.PolicyDraftRepository; | |
| import com.dalab.policyengine.repository.PolicyRepository; | |
| // Add missing JPA criteria import | |
| import jakarta.persistence.criteria.Predicate; | |
| public class PolicyService implements IPolicyService { | |
| private static final Logger log = LoggerFactory.getLogger(PolicyService.class); | |
| private final PolicyRepository policyRepository; | |
| private final PolicyMapper policyMapper; | |
| // Draft management dependencies | |
| private final PolicyDraftRepository policyDraftRepository; | |
| private final PolicyDraftMapper policyDraftMapper; | |
| public PolicyService(PolicyRepository policyRepository, PolicyMapper policyMapper, | |
| PolicyDraftRepository policyDraftRepository, PolicyDraftMapper policyDraftMapper) { | |
| this.policyRepository = policyRepository; | |
| this.policyMapper = policyMapper; | |
| this.policyDraftRepository = policyDraftRepository; | |
| this.policyDraftMapper = policyDraftMapper; | |
| } | |
| public Page<PolicySummaryDTO> getAllPolicies(Pageable pageable, String status, String nameContains) { | |
| Specification<Policy> spec = (root, query, criteriaBuilder) -> { | |
| List<Predicate> predicates = new ArrayList<>(); | |
| if (StringUtils.hasText(status)) { | |
| try { | |
| predicates.add(criteriaBuilder.equal(root.get("status"), PolicyStatus.valueOf(status.toUpperCase()))); | |
| } catch (IllegalArgumentException e) { | |
| log.warn("Invalid policy status provided: {}", status); | |
| // Option: throw bad request, or ignore filter, or return empty | |
| // Returning empty for now by adding a certainly false predicate | |
| predicates.add(criteriaBuilder.disjunction()); | |
| } | |
| } | |
| if (StringUtils.hasText(nameContains)) { | |
| predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), | |
| "%" + nameContains.toLowerCase() + "%")); | |
| } | |
| return criteriaBuilder.and(predicates.toArray(new Predicate[0])); | |
| }; | |
| return policyRepository.findAll(spec, pageable).map(policyMapper::toPolicySummaryDTO); | |
| } | |
| public PolicyOutputDTO getPolicyById(UUID policyId) { | |
| Policy policy = policyRepository.findById(policyId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); | |
| return policyMapper.toPolicyOutputDTO(policy); | |
| } | |
| public PolicyOutputDTO createPolicy(PolicyInputDTO policyInputDTO, UUID creatorUserId) { | |
| policyRepository.findByName(policyInputDTO.getName()).ifPresent(p -> { | |
| throw new ConflictException("Policy with name '" + policyInputDTO.getName() + "' already exists."); | |
| }); | |
| Policy policy = policyMapper.toPolicyEntity(policyInputDTO); | |
| policy.setCreatedByUserId(creatorUserId); | |
| // createdAt and updatedAt are set by @PrePersist | |
| Policy savedPolicy = policyRepository.save(policy); | |
| log.info("Created policy {} with id {}", savedPolicy.getName(), savedPolicy.getId()); | |
| return policyMapper.toPolicyOutputDTO(savedPolicy); | |
| } | |
| public PolicyOutputDTO updatePolicy(UUID policyId, PolicyInputDTO policyInputDTO, UUID updaterUserId) { | |
| Policy existingPolicy = policyRepository.findById(policyId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy", "id", policyId.toString())); | |
| // Check for name conflict if name is being changed | |
| if (!existingPolicy.getName().equals(policyInputDTO.getName())) { | |
| policyRepository.findByName(policyInputDTO.getName()).ifPresent(p -> { | |
| if (!p.getId().equals(existingPolicy.getId())) { | |
| throw new ConflictException("Another policy with name '" + policyInputDTO.getName() + "' already exists."); | |
| } | |
| }); | |
| } | |
| policyMapper.updatePolicyEntityFromInputDTO(existingPolicy, policyInputDTO); | |
| existingPolicy.setUpdatedByUserId(updaterUserId); | |
| // updatedAt is set by @PreUpdate | |
| Policy updatedPolicy = policyRepository.save(existingPolicy); | |
| log.info("Updated policy {} with id {}", updatedPolicy.getName(), updatedPolicy.getId()); | |
| return policyMapper.toPolicyOutputDTO(updatedPolicy); | |
| } | |
| public void deletePolicy(UUID policyId) { | |
| if (!policyRepository.existsById(policyId)) { | |
| throw new ResourceNotFoundException("Policy", "id", policyId.toString()); | |
| } | |
| // Consider implications: what about active evaluations? Soft delete? | |
| // For now, direct delete. | |
| policyRepository.deleteById(policyId); | |
| log.info("Deleted policy with id {}", policyId); | |
| } | |
| public PolicyImpactResponseDTO analyzePolicy(PolicyImpactRequestDTO request) { | |
| log.info("Analyzing policy impact for rules content with analysis type: {}", request.getAnalysisType()); | |
| long startTime = System.currentTimeMillis(); | |
| // Generate unique analysis ID | |
| String analysisId = "impact-" + UUID.randomUUID().toString().substring(0, 8); | |
| // Create response with comprehensive mock data | |
| PolicyImpactResponseDTO response = new PolicyImpactResponseDTO(analysisId, request.getAnalysisType()); | |
| // Build impact summary based on analysis type | |
| PolicyImpactResponseDTO.ImpactSummaryDTO summary = createImpactSummary(request.getAnalysisType()); | |
| response.setSummary(summary); | |
| // Build affected assets list | |
| List<PolicyImpactResponseDTO.AssetImpactDTO> affectedAssets = createAffectedAssetsList(request); | |
| response.setAffectedAssets(affectedAssets); | |
| // Add performance impact if requested | |
| if (Boolean.TRUE.equals(request.getIncludePerformanceEstimate())) { | |
| PolicyImpactResponseDTO.PerformanceImpactDTO performanceImpact = createPerformanceImpact(request.getAnalysisType()); | |
| response.setPerformanceImpact(performanceImpact); | |
| } | |
| // Add cost impact if requested | |
| if (Boolean.TRUE.equals(request.getIncludeCostImpact())) { | |
| PolicyImpactResponseDTO.CostImpactDTO costImpact = createCostImpact(request.getAnalysisType()); | |
| response.setCostImpact(costImpact); | |
| } | |
| // Add compliance impact if requested | |
| if (Boolean.TRUE.equals(request.getIncludeComplianceImpact())) { | |
| PolicyImpactResponseDTO.ComplianceImpactDTO complianceImpact = createComplianceImpact(); | |
| response.setComplianceImpact(complianceImpact); | |
| } | |
| // Build risk assessment | |
| PolicyImpactResponseDTO.RiskAssessmentDTO riskAssessment = createRiskAssessment(summary); | |
| response.setRiskAssessment(riskAssessment); | |
| // Build recommendations | |
| List<String> recommendations = createRecommendations(request, summary); | |
| response.setRecommendations(recommendations); | |
| // Build metadata | |
| long executionTime = System.currentTimeMillis() - startTime; | |
| PolicyImpactResponseDTO.AnalysisMetadataDTO metadata = createAnalysisMetadata(executionTime); | |
| response.setMetadata(metadata); | |
| log.info("Policy impact analysis completed in {}ms for analysis ID: {}", executionTime, analysisId); | |
| return response; | |
| } | |
| /** | |
| * Create impact summary based on analysis type with realistic metrics. | |
| */ | |
| private PolicyImpactResponseDTO.ImpactSummaryDTO createImpactSummary(String analysisType) { | |
| PolicyImpactResponseDTO.ImpactSummaryDTO summary = new PolicyImpactResponseDTO.ImpactSummaryDTO(); | |
| switch (analysisType.toUpperCase()) { | |
| case "FULL": | |
| summary.setTotalAssetsAnalyzed(2847); | |
| summary.setTotalAssetsAffected(1234); | |
| summary.setHighImpactAssets(89); | |
| summary.setMediumImpactAssets(456); | |
| summary.setLowImpactAssets(689); | |
| summary.setOverallRiskLevel("MEDIUM"); | |
| summary.setImpactPercentage(43.4); | |
| break; | |
| case "QUICK": | |
| summary.setTotalAssetsAnalyzed(500); | |
| summary.setTotalAssetsAffected(218); | |
| summary.setHighImpactAssets(15); | |
| summary.setMediumImpactAssets(78); | |
| summary.setLowImpactAssets(125); | |
| summary.setOverallRiskLevel("LOW"); | |
| summary.setImpactPercentage(43.6); | |
| break; | |
| case "TARGETED": | |
| summary.setTotalAssetsAnalyzed(156); | |
| summary.setTotalAssetsAffected(89); | |
| summary.setHighImpactAssets(12); | |
| summary.setMediumImpactAssets(34); | |
| summary.setLowImpactAssets(43); | |
| summary.setOverallRiskLevel("HIGH"); | |
| summary.setImpactPercentage(57.1); | |
| break; | |
| default: | |
| summary.setTotalAssetsAnalyzed(1000); | |
| summary.setTotalAssetsAffected(420); | |
| summary.setHighImpactAssets(35); | |
| summary.setMediumImpactAssets(150); | |
| summary.setLowImpactAssets(235); | |
| summary.setOverallRiskLevel("MEDIUM"); | |
| summary.setImpactPercentage(42.0); | |
| } | |
| return summary; | |
| } | |
| /** | |
| * Create list of affected assets with detailed impact information. | |
| */ | |
| private List<PolicyImpactResponseDTO.AssetImpactDTO> createAffectedAssetsList(PolicyImpactRequestDTO request) { | |
| List<PolicyImpactResponseDTO.AssetImpactDTO> assets = new ArrayList<>(); | |
| // Sample high-impact asset | |
| PolicyImpactResponseDTO.AssetImpactDTO highImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); | |
| highImpactAsset.setAssetId("asset-db-customer-001"); | |
| highImpactAsset.setAssetName("Customer Database - Production"); | |
| highImpactAsset.setAssetType("database"); | |
| highImpactAsset.setImpactLevel("HIGH"); | |
| highImpactAsset.setAffectedAttributes(Arrays.asList("personal_data", "financial_data", "contact_info")); | |
| highImpactAsset.setAppliedActions(Arrays.asList("auto_encrypt", "access_log", "compliance_tag")); | |
| highImpactAsset.setRiskAssessment("High business impact due to customer data sensitivity and compliance requirements"); | |
| Map<String, Object> highImpactDetails = new HashMap<>(); | |
| highImpactDetails.put("recordCount", 1250000); | |
| highImpactDetails.put("dataTypes", Arrays.asList("PII", "Financial", "Health")); | |
| highImpactDetails.put("complianceFrameworks", Arrays.asList("GDPR", "PCI-DSS", "HIPAA")); | |
| highImpactAsset.setImpactDetails(highImpactDetails); | |
| assets.add(highImpactAsset); | |
| // Sample medium-impact asset | |
| PolicyImpactResponseDTO.AssetImpactDTO mediumImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); | |
| mediumImpactAsset.setAssetId("asset-file-logs-002"); | |
| mediumImpactAsset.setAssetName("Application Logs - Analytics"); | |
| mediumImpactAsset.setAssetType("file"); | |
| mediumImpactAsset.setImpactLevel("MEDIUM"); | |
| mediumImpactAsset.setAffectedAttributes(Arrays.asList("user_sessions", "performance_metrics")); | |
| mediumImpactAsset.setAppliedActions(Arrays.asList("retention_policy", "anonymize")); | |
| mediumImpactAsset.setRiskAssessment("Moderate impact on analytics capabilities with potential performance implications"); | |
| Map<String, Object> mediumImpactDetails = new HashMap<>(); | |
| mediumImpactDetails.put("fileSizeGB", 45.7); | |
| mediumImpactDetails.put("retentionPeriodDays", 90); | |
| mediumImpactDetails.put("accessFrequency", "daily"); | |
| mediumImpactAsset.setImpactDetails(mediumImpactDetails); | |
| assets.add(mediumImpactAsset); | |
| // Sample low-impact asset | |
| PolicyImpactResponseDTO.AssetImpactDTO lowImpactAsset = new PolicyImpactResponseDTO.AssetImpactDTO(); | |
| lowImpactAsset.setAssetId("asset-api-public-003"); | |
| lowImpactAsset.setAssetName("Public API Documentation"); | |
| lowImpactAsset.setAssetType("api"); | |
| lowImpactAsset.setImpactLevel("LOW"); | |
| lowImpactAsset.setAffectedAttributes(Arrays.asList("endpoint_metadata")); | |
| lowImpactAsset.setAppliedActions(Arrays.asList("classification_tag")); | |
| lowImpactAsset.setRiskAssessment("Minimal impact on public-facing documentation"); | |
| Map<String, Object> lowImpactDetails = new HashMap<>(); | |
| lowImpactDetails.put("endpointCount", 127); | |
| lowImpactDetails.put("publicAccess", true); | |
| lowImpactDetails.put("lastUpdated", "2024-12-15"); | |
| lowImpactAsset.setImpactDetails(lowImpactDetails); | |
| assets.add(lowImpactAsset); | |
| return assets; | |
| } | |
| /** | |
| * Create performance impact estimates based on analysis type. | |
| */ | |
| private PolicyImpactResponseDTO.PerformanceImpactDTO createPerformanceImpact(String analysisType) { | |
| PolicyImpactResponseDTO.PerformanceImpactDTO performance = new PolicyImpactResponseDTO.PerformanceImpactDTO(); | |
| switch (analysisType.toUpperCase()) { | |
| case "FULL": | |
| performance.setEstimatedProcessingTimeMs(45000L); | |
| performance.setCpuUtilizationIncrease(15.7); | |
| performance.setMemoryUtilizationIncrease(8.3); | |
| performance.setEstimatedApiCalls(2847); | |
| performance.setPerformanceRiskLevel("MEDIUM"); | |
| break; | |
| case "QUICK": | |
| performance.setEstimatedProcessingTimeMs(5200L); | |
| performance.setCpuUtilizationIncrease(3.2); | |
| performance.setMemoryUtilizationIncrease(2.1); | |
| performance.setEstimatedApiCalls(350); | |
| performance.setPerformanceRiskLevel("LOW"); | |
| break; | |
| case "TARGETED": | |
| performance.setEstimatedProcessingTimeMs(8500L); | |
| performance.setCpuUtilizationIncrease(5.8); | |
| performance.setMemoryUtilizationIncrease(3.4); | |
| performance.setEstimatedApiCalls(156); | |
| performance.setPerformanceRiskLevel("LOW"); | |
| break; | |
| default: | |
| performance.setEstimatedProcessingTimeMs(15000L); | |
| performance.setCpuUtilizationIncrease(7.5); | |
| performance.setMemoryUtilizationIncrease(4.2); | |
| performance.setEstimatedApiCalls(1000); | |
| performance.setPerformanceRiskLevel("MEDIUM"); | |
| } | |
| return performance; | |
| } | |
| /** | |
| * Create cost impact analysis with estimated costs and savings. | |
| */ | |
| private PolicyImpactResponseDTO.CostImpactDTO createCostImpact(String analysisType) { | |
| PolicyImpactResponseDTO.CostImpactDTO cost = new PolicyImpactResponseDTO.CostImpactDTO(); | |
| cost.setEstimatedMonthlyCost(2847.50); | |
| cost.setEstimatedImplementationCost(15750.00); | |
| cost.setPotentialSavings(8450.25); | |
| cost.setCostRiskLevel("MEDIUM"); | |
| Map<String, Double> breakdown = new HashMap<>(); | |
| breakdown.put("compute_resources", 1200.00); | |
| breakdown.put("storage_costs", 675.50); | |
| breakdown.put("api_calls", 425.75); | |
| breakdown.put("compliance_tools", 546.25); | |
| cost.setCostBreakdown(breakdown); | |
| return cost; | |
| } | |
| /** | |
| * Create compliance impact analysis. | |
| */ | |
| private PolicyImpactResponseDTO.ComplianceImpactDTO createComplianceImpact() { | |
| PolicyImpactResponseDTO.ComplianceImpactDTO compliance = new PolicyImpactResponseDTO.ComplianceImpactDTO(); | |
| compliance.setConflictingPolicies(2); | |
| compliance.setComplianceFrameworksAffected(Arrays.asList("GDPR", "PCI-DSS", "SOX", "HIPAA")); | |
| compliance.setComplianceRiskLevel("MEDIUM"); | |
| compliance.setPotentialViolations(Arrays.asList( | |
| "Data retention period conflict with existing archival policy", | |
| "Encryption requirements may override current security policy" | |
| )); | |
| return compliance; | |
| } | |
| /** | |
| * Create comprehensive risk assessment. | |
| */ | |
| private PolicyImpactResponseDTO.RiskAssessmentDTO createRiskAssessment(PolicyImpactResponseDTO.ImpactSummaryDTO summary) { | |
| PolicyImpactResponseDTO.RiskAssessmentDTO risk = new PolicyImpactResponseDTO.RiskAssessmentDTO(); | |
| risk.setOverallRiskLevel(summary.getOverallRiskLevel()); | |
| risk.setRiskScore(calculateRiskScore(summary)); | |
| List<String> risks = Arrays.asList( | |
| "High-volume data processing may impact system performance", | |
| "Compliance conflicts require manual review and resolution", | |
| "Implementation costs exceed initial estimates", | |
| "Customer data access patterns may be disrupted" | |
| ); | |
| risk.setIdentifiedRisks(risks); | |
| List<String> mitigations = Arrays.asList( | |
| "Implement gradual rollout with performance monitoring", | |
| "Conduct compliance review before full deployment", | |
| "Establish cost monitoring and alerting mechanisms", | |
| "Create customer communication plan for access changes" | |
| ); | |
| risk.setMitigationStrategies(mitigations); | |
| return risk; | |
| } | |
| /** | |
| * Calculate numeric risk score based on impact summary. | |
| */ | |
| private Double calculateRiskScore(PolicyImpactResponseDTO.ImpactSummaryDTO summary) { | |
| double baseScore = 50.0; | |
| // Adjust based on impact level distribution | |
| if (summary.getHighImpactAssets() != null) { | |
| baseScore += summary.getHighImpactAssets() * 0.5; | |
| } | |
| if (summary.getMediumImpactAssets() != null) { | |
| baseScore += summary.getMediumImpactAssets() * 0.2; | |
| } | |
| // Adjust based on overall impact percentage | |
| if (summary.getImpactPercentage() != null) { | |
| baseScore += (summary.getImpactPercentage() - 30.0) * 0.8; | |
| } | |
| // Cap the score between 0 and 100 | |
| return Math.max(0.0, Math.min(100.0, baseScore)); | |
| } | |
| /** | |
| * Create implementation recommendations based on analysis. | |
| */ | |
| private List<String> createRecommendations(PolicyImpactRequestDTO request, PolicyImpactResponseDTO.ImpactSummaryDTO summary) { | |
| List<String> recommendations = new ArrayList<>(); | |
| // Risk-based recommendations | |
| switch (summary.getOverallRiskLevel()) { | |
| case "HIGH": | |
| recommendations.add("Consider phased implementation starting with low-impact assets"); | |
| recommendations.add("Establish comprehensive rollback procedures"); | |
| recommendations.add("Increase monitoring and alerting during implementation"); | |
| break; | |
| case "MEDIUM": | |
| recommendations.add("Implement with standard change management procedures"); | |
| recommendations.add("Monitor key performance indicators during rollout"); | |
| break; | |
| case "LOW": | |
| recommendations.add("Proceed with standard implementation timeline"); | |
| recommendations.add("Standard post-implementation review recommended"); | |
| break; | |
| } | |
| // Analysis type specific recommendations | |
| if ("QUICK".equals(request.getAnalysisType())) { | |
| recommendations.add("Consider running FULL analysis before production deployment"); | |
| } | |
| // Performance-based recommendations | |
| if (Boolean.TRUE.equals(request.getIncludePerformanceEstimate())) { | |
| recommendations.add("Schedule implementation during low-traffic periods"); | |
| recommendations.add("Prepare additional compute resources for initial processing"); | |
| } | |
| return recommendations; | |
| } | |
| /** | |
| * Create analysis execution metadata. | |
| */ | |
| private PolicyImpactResponseDTO.AnalysisMetadataDTO createAnalysisMetadata(long executionTime) { | |
| PolicyImpactResponseDTO.AnalysisMetadataDTO metadata = new PolicyImpactResponseDTO.AnalysisMetadataDTO(); | |
| metadata.setExecutionTimeMs(executionTime); | |
| metadata.setAnalysisVersion("v2.4.1"); | |
| metadata.setUsesCachedData(false); | |
| metadata.setDataFreshnessTimestamp(Instant.now().minusSeconds(300)); // 5 minutes ago | |
| return metadata; | |
| } | |
| // ========== POLICY DRAFT MANAGEMENT IMPLEMENTATION ========== | |
| public PolicyDraftOutputDTO createDraft(PolicyDraftInputDTO draftInput, UUID creatorUserId) { | |
| log.info("Creating new policy draft: {}", draftInput.getName()); | |
| // Validate draft name uniqueness | |
| if (policyDraftRepository.existsByName(draftInput.getName())) { | |
| throw new ConflictException("A draft with this name already exists"); | |
| } | |
| // Convert DTO to entity | |
| PolicyDraft draft = policyDraftMapper.toEntity(draftInput); | |
| draft.setCreatedByUserId(creatorUserId); | |
| draft.setStatus(PolicyDraftStatus.CREATED); | |
| // Save the draft | |
| PolicyDraft savedDraft = policyDraftRepository.save(draft); | |
| log.info("Successfully created policy draft with ID: {}", savedDraft.getId()); | |
| return policyDraftMapper.toOutputDTO(savedDraft); | |
| } | |
| public PolicyDraftOutputDTO getDraftById(UUID draftId) { | |
| log.debug("Retrieving policy draft with ID: {}", draftId); | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| return policyDraftMapper.toOutputDTO(draft); | |
| } | |
| public PolicyDraftOutputDTO updateDraft(UUID draftId, PolicyDraftInputDTO draftInput, UUID updaterUserId) { | |
| log.info("Updating policy draft with ID: {}", draftId); | |
| PolicyDraft existingDraft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| // Validate that draft can be updated | |
| if (!canEditDraft(existingDraft.getStatus())) { | |
| throw new ConflictException("Draft cannot be edited in current status: " + existingDraft.getStatus()); | |
| } | |
| // Validate name uniqueness (excluding current draft) | |
| if (!draftInput.getName().equals(existingDraft.getName()) && | |
| policyDraftRepository.existsByNameExcludingId(draftInput.getName(), draftId)) { | |
| throw new ConflictException("A draft with this name already exists"); | |
| } | |
| // Update the draft | |
| policyDraftMapper.updateEntity(existingDraft, draftInput); | |
| existingDraft.setUpdatedByUserId(updaterUserId); | |
| PolicyDraft savedDraft = policyDraftRepository.save(existingDraft); | |
| log.info("Successfully updated policy draft with ID: {}", savedDraft.getId()); | |
| return policyDraftMapper.toOutputDTO(savedDraft); | |
| } | |
| public PolicyDraftOutputDTO submitDraft(UUID draftId, PolicyDraftActionDTO action, UUID submittedByUserId) { | |
| log.info("Submitting policy draft with ID: {}", draftId); | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| // Validate status transition | |
| if (!canSubmitDraft(draft.getStatus())) { | |
| throw new ConflictException("Draft cannot be submitted in current status: " + draft.getStatus()); | |
| } | |
| // Update draft status and metadata | |
| draft.setStatus(PolicyDraftStatus.SUBMITTED); | |
| draft.setSubmittedAt(Instant.now()); | |
| draft.setSubmittedByUserId(submittedByUserId); | |
| draft.setUpdatedByUserId(submittedByUserId); | |
| // Add comment if provided | |
| if (action.getComment() != null && !action.getComment().trim().isEmpty()) { | |
| draft.addReviewComment("Submission: " + action.getComment(), submittedByUserId, "SUBMITTER"); | |
| } | |
| PolicyDraft savedDraft = policyDraftRepository.save(draft); | |
| log.info("Successfully submitted policy draft with ID: {}", savedDraft.getId()); | |
| return policyDraftMapper.toOutputDTO(savedDraft); | |
| } | |
| public PolicyDraftOutputDTO approveDraft(UUID draftId, PolicyDraftActionDTO action, UUID approverUserId) { | |
| log.info("Approving policy draft with ID: {}", draftId); | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| // Validate status transition | |
| if (!canApproveDraft(draft.getStatus())) { | |
| throw new ConflictException("Draft cannot be approved in current status: " + draft.getStatus()); | |
| } | |
| // Update draft status and metadata | |
| draft.setStatus(PolicyDraftStatus.APPROVED); | |
| draft.setApprovedAt(Instant.now()); | |
| draft.setApprovedByUserId(approverUserId); | |
| draft.setUpdatedByUserId(approverUserId); | |
| // Add comment if provided | |
| if (action.getComment() != null && !action.getComment().trim().isEmpty()) { | |
| draft.addReviewComment("Approval: " + action.getComment(), approverUserId, "APPROVER"); | |
| } | |
| PolicyDraft savedDraft = policyDraftRepository.save(draft); | |
| log.info("Successfully approved policy draft with ID: {}", savedDraft.getId()); | |
| return policyDraftMapper.toOutputDTO(savedDraft); | |
| } | |
| public PolicyDraftOutputDTO rejectDraft(UUID draftId, PolicyDraftActionDTO action, UUID rejectorUserId) { | |
| log.info("Rejecting policy draft with ID: {}", draftId); | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| // Validate status transition | |
| if (!canRejectDraft(draft.getStatus())) { | |
| throw new ConflictException("Draft cannot be rejected in current status: " + draft.getStatus()); | |
| } | |
| // Rejection requires a comment | |
| if (action.getComment() == null || action.getComment().trim().isEmpty()) { | |
| throw new IllegalArgumentException("Comment is required for draft rejection"); | |
| } | |
| // Update draft status and metadata | |
| draft.setStatus(PolicyDraftStatus.REJECTED); | |
| draft.setRejectedAt(Instant.now()); | |
| draft.setRejectedByUserId(rejectorUserId); | |
| draft.setUpdatedByUserId(rejectorUserId); | |
| // Add rejection comment | |
| draft.addReviewComment("Rejection: " + action.getComment(), rejectorUserId, "REJECTOR"); | |
| PolicyDraft savedDraft = policyDraftRepository.save(draft); | |
| log.info("Successfully rejected policy draft with ID: {}", savedDraft.getId()); | |
| return policyDraftMapper.toOutputDTO(savedDraft); | |
| } | |
| public PolicyDraftOutputDTO requestChanges(UUID draftId, PolicyDraftActionDTO action, UUID reviewerUserId) { | |
| log.info("Requesting changes for policy draft with ID: {}", draftId); | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| // Validate status transition | |
| if (!canRequestChanges(draft.getStatus())) { | |
| throw new ConflictException("Changes cannot be requested for draft in current status: " + draft.getStatus()); | |
| } | |
| // Change request requires a comment | |
| if (action.getComment() == null || action.getComment().trim().isEmpty()) { | |
| throw new IllegalArgumentException("Comment is required for change request"); | |
| } | |
| // Update draft status and metadata | |
| draft.setStatus(PolicyDraftStatus.REQUIRES_CHANGES); | |
| draft.setUpdatedByUserId(reviewerUserId); | |
| // Add change request comment | |
| draft.addReviewComment("Change Request: " + action.getComment(), reviewerUserId, "REVIEWER"); | |
| PolicyDraft savedDraft = policyDraftRepository.save(draft); | |
| log.info("Successfully requested changes for policy draft with ID: {}", savedDraft.getId()); | |
| return policyDraftMapper.toOutputDTO(savedDraft); | |
| } | |
| public PolicyOutputDTO publishDraft(UUID draftId, PolicyDraftActionDTO action, UUID publisherUserId) { | |
| log.info("Publishing policy draft with ID: {}", draftId); | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| // Validate status | |
| if (draft.getStatus() != PolicyDraftStatus.APPROVED) { | |
| throw new ConflictException("Only approved drafts can be published"); | |
| } | |
| // Create policy from draft | |
| PolicyInputDTO policyInput = convertDraftToPolicy(draft); | |
| PolicyOutputDTO publishedPolicy = createPolicy(policyInput, publisherUserId); | |
| // Update draft status | |
| draft.setStatus(PolicyDraftStatus.PUBLISHED); | |
| draft.setPublishedAt(Instant.now()); | |
| draft.setPublishedByUserId(publisherUserId); | |
| draft.setUpdatedByUserId(publisherUserId); | |
| // Add publication comment | |
| if (action.getComment() != null && !action.getComment().trim().isEmpty()) { | |
| draft.addReviewComment("Publication: " + action.getComment(), publisherUserId, "PUBLISHER"); | |
| } | |
| policyDraftRepository.save(draft); | |
| log.info("Successfully published policy draft {} as policy {}", draftId, publishedPolicy.getId()); | |
| return publishedPolicy; | |
| } | |
| // Helper methods for validation | |
| private boolean canEditDraft(PolicyDraftStatus status) { | |
| return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; | |
| } | |
| private boolean canSubmitDraft(PolicyDraftStatus status) { | |
| return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; | |
| } | |
| private boolean canApproveDraft(PolicyDraftStatus status) { | |
| return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; | |
| } | |
| private boolean canRejectDraft(PolicyDraftStatus status) { | |
| return status == PolicyDraftStatus.SUBMITTED || | |
| status == PolicyDraftStatus.UNDER_REVIEW || | |
| status == PolicyDraftStatus.APPROVED; | |
| } | |
| private boolean canRequestChanges(PolicyDraftStatus status) { | |
| return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; | |
| } | |
| // Convert draft to policy for publication | |
| private PolicyInputDTO convertDraftToPolicy(PolicyDraft draft) { | |
| PolicyInputDTO policyInput = new PolicyInputDTO(); | |
| policyInput.setName(draft.getName()); | |
| policyInput.setDescription(draft.getDescription()); | |
| policyInput.setConditionLogic(draft.getConditionLogic()); | |
| policyInput.setActions(draft.getActions()); | |
| // Convert rules definition to PolicyRule format if needed | |
| // This is simplified - real implementation would convert JSON rules to PolicyRule entities | |
| return policyInput; | |
| } | |
| // Placeholder implementations for remaining methods (to resolve compilation errors) | |
| public Page<PolicyDraftOutputDTO> getAllDrafts(Pageable pageable, PolicyDraftStatus status, String category, String priority, UUID createdBy, String nameContains) { | |
| // TODO: Implement comprehensive search with filters | |
| Page<PolicyDraft> drafts = policyDraftRepository.findAll(pageable); | |
| return drafts.map(policyDraftMapper::toOutputDTO); | |
| } | |
| public Page<PolicyDraftOutputDTO> getDraftsRequiringAttention(UUID userId, Pageable pageable) { | |
| Page<PolicyDraft> drafts = policyDraftRepository.findRequiringAttention(userId, pageable); | |
| return drafts.map(policyDraftMapper::toOutputDTO); | |
| } | |
| public Page<PolicyDraftOutputDTO> getDraftsPendingReview(Pageable pageable) { | |
| Page<PolicyDraft> drafts = policyDraftRepository.findPendingReview(pageable); | |
| return drafts.map(policyDraftMapper::toOutputDTO); | |
| } | |
| public PolicyDraftOutputDTO archiveDraft(UUID draftId, PolicyDraftActionDTO action, UUID archiverUserId) { | |
| // TODO: Implement archive functionality | |
| throw new UnsupportedOperationException("Archive functionality not yet implemented"); | |
| } | |
| public PolicyDraftOutputDTO addReviewComment(UUID draftId, String comment, UUID reviewerUserId, String reviewerRole) { | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| draft.addReviewComment(comment, reviewerUserId, reviewerRole); | |
| PolicyDraft savedDraft = policyDraftRepository.save(draft); | |
| return policyDraftMapper.toOutputDTO(savedDraft); | |
| } | |
| public void deleteDraft(UUID draftId, UUID deleterUserId) { | |
| PolicyDraft draft = policyDraftRepository.findById(draftId) | |
| .orElseThrow(() -> new ResourceNotFoundException("Policy draft not found with ID: " + draftId)); | |
| if (draft.getStatus() != PolicyDraftStatus.CREATED) { | |
| throw new ConflictException("Only drafts in CREATED status can be deleted"); | |
| } | |
| policyDraftRepository.delete(draft); | |
| log.info("Successfully deleted policy draft with ID: {}", draftId); | |
| } | |
| public List<String> getDraftCategories() { | |
| return policyDraftRepository.findAllCategories(); | |
| } | |
| public List<String> getDraftTags() { | |
| return policyDraftRepository.findAllTags(); | |
| } | |
| public List<Object[]> getDraftStatistics() { | |
| return policyDraftRepository.getDraftStatisticsByStatus(); | |
| } | |
| public List<PolicyDraftOutputDTO> getOverdueDrafts() { | |
| List<PolicyDraft> overdueDrafts = policyDraftRepository.findOverdueDrafts(Instant.now()); | |
| return overdueDrafts.stream() | |
| .map(policyDraftMapper::toOutputDTO) | |
| .collect(java.util.stream.Collectors.toList()); | |
| } | |
| public PolicyDraftOutputDTO clonePolicyAsDraft(UUID policyId, UUID creatorUserId) { | |
| // TODO: Implement policy cloning functionality | |
| throw new UnsupportedOperationException("Policy cloning functionality not yet implemented"); | |
| } | |
| public PolicyDraftOutputDTO createDraftVersion(UUID draftId, PolicyDraftInputDTO draftInput, UUID creatorUserId) { | |
| // TODO: Implement versioning functionality | |
| throw new UnsupportedOperationException("Draft versioning functionality not yet implemented"); | |
| } | |
| } |