dalabai's picture
Upload folder using huggingface_hub
9373c61 verified
raw
history blame
38.5 kB
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<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);
}
@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<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 ==========
@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<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);
}
@Override
public Page<PolicyDraftOutputDTO> getDraftsRequiringAttention(UUID userId, Pageable pageable) {
Page<PolicyDraft> drafts = policyDraftRepository.findRequiringAttention(userId, pageable);
return drafts.map(policyDraftMapper::toOutputDTO);
}
@Override
public Page<PolicyDraftOutputDTO> getDraftsPendingReview(Pageable pageable) {
Page<PolicyDraft> 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<String> getDraftCategories() {
return policyDraftRepository.findAllCategories();
}
@Override
public List<String> getDraftTags() {
return policyDraftRepository.findAllTags();
}
@Override
public List<Object[]> getDraftStatistics() {
return policyDraftRepository.getDraftStatisticsByStatus();
}
@Override
public List<PolicyDraftOutputDTO> getOverdueDrafts() {
List<PolicyDraft> 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");
}
}