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