google-site-verification: google61fe8ba583a51912.html
GDPR Compliance Toolkit in Java

Overview

A comprehensive toolkit for implementing GDPR compliance features in Java applications, including data subject rights, consent management, data processing records, and privacy by design.

1. Core GDPR Data Models

import javax.persistence.*; import javax.validation.constraints.*; import java.time.LocalDateTime; import java.util.*; @Entity @Table(name = "data_subjects") public class DataSubject { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Column(unique = true) private String subjectId; // External identifier private String email; private String phone; @Embedded private PersonalData personalData; @OneToMany(mappedBy = "dataSubject", cascade = CascadeType.ALL) private List<ConsentRecord> consents = new ArrayList<>(); @OneToMany(mappedBy = "dataSubject", cascade = CascadeType.ALL) private List<DataAccessRequest> accessRequests = new ArrayList<>(); @OneToMany(mappedBy = "dataSubject", cascade = CascadeType.ALL) private List<ProcessingActivity> processingActivities = new ArrayList<>(); private LocalDateTime createdAt; private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getSubjectId() { return subjectId; } public void setSubjectId(String subjectId) { this.subjectId = subjectId; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public PersonalData getPersonalData() { return personalData; } public void setPersonalData(PersonalData personalData) { this.personalData = personalData; } public List<ConsentRecord> getConsents() { return consents; } public void setConsents(List<ConsentRecord> consents) { this.consents = consents; } public List<DataAccessRequest> getAccessRequests() { return accessRequests; } public void setAccessRequests(List<DataAccessRequest> accessRequests) { this.accessRequests = accessRequests; } public List<ProcessingActivity> getProcessingActivities() { return processingActivities; } public void setProcessingActivities(List<ProcessingActivity> processingActivities) { this.processingActivities = processingActivities; } public LocalDateTime getCreatedAt() { return createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } } @Embeddable public class PersonalData { private String firstName; private String lastName; private LocalDate dateOfBirth; private String address; private String city; private String country; private String postalCode; // Getters and setters public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public LocalDate getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(LocalDate dateOfBirth) { this.dateOfBirth = dateOfBirth; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getPostalCode() { return postalCode; } public void setPostalCode(String postalCode) { this.postalCode = postalCode; } } @Entity @Table(name = "consent_records") public class ConsentRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "data_subject_id") private DataSubject dataSubject; @NotBlank private String purpose; @NotBlank private String legalBasis; // CONSENT, CONTRACT, LEGAL_OBLIGATION, etc. private boolean granted; @Lob private String consentText; private String version; private LocalDateTime grantedAt; private LocalDateTime revokedAt; private LocalDateTime expiresAt; private String ipAddress; private String userAgent; // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public DataSubject getDataSubject() { return dataSubject; } public void setDataSubject(DataSubject dataSubject) { this.dataSubject = dataSubject; } public String getPurpose() { return purpose; } public void setPurpose(String purpose) { this.purpose = purpose; } public String getLegalBasis() { return legalBasis; } public void setLegalBasis(String legalBasis) { this.legalBasis = legalBasis; } public boolean isGranted() { return granted; } public void setGranted(boolean granted) { this.granted = granted; } public String getConsentText() { return consentText; } public void setConsentText(String consentText) { this.consentText = consentText; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public LocalDateTime getGrantedAt() { return grantedAt; } public void setGrantedAt(LocalDateTime grantedAt) { this.grantedAt = grantedAt; } public LocalDateTime getRevokedAt() { return revokedAt; } public void setRevokedAt(LocalDateTime revokedAt) { this.revokedAt = revokedAt; } public LocalDateTime getExpiresAt() { return expiresAt; } public void setExpiresAt(LocalDateTime expiresAt) { this.expiresAt = expiresAt; } public String getIpAddress() { return ipAddress; } public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } public String getUserAgent() { return userAgent; } public void setUserAgent(String userAgent) { this.userAgent = userAgent; } } @Entity @Table(name = "data_access_requests") public class DataAccessRequest { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "data_subject_id") private DataSubject dataSubject; @Enumerated(EnumType.STRING) private RequestType type; @Enumerated(EnumType.STRING) private RequestStatus status = RequestStatus.PENDING; private LocalDateTime requestedAt; private LocalDateTime completedAt; private LocalDateTime dueDate; @Lob private String requestDetails; @Lob private String responseData; private String assignedTo; // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public DataSubject getDataSubject() { return dataSubject; } public void setDataSubject(DataSubject dataSubject) { this.dataSubject = dataSubject; } public RequestType getType() { return type; } public void setType(RequestType type) { this.type = type; } public RequestStatus getStatus() { return status; } public void setStatus(RequestStatus status) { this.status = status; } public LocalDateTime getRequestedAt() { return requestedAt; } public void setRequestedAt(LocalDateTime requestedAt) { this.requestedAt = requestedAt; } public LocalDateTime getCompletedAt() { return completedAt; } public void setCompletedAt(LocalDateTime completedAt) { this.completedAt = completedAt; } public LocalDateTime getDueDate() { return dueDate; } public void setDueDate(LocalDateTime dueDate) { this.dueDate = dueDate; } public String getRequestDetails() { return requestDetails; } public void setRequestDetails(String requestDetails) { this.requestDetails = requestDetails; } public String getResponseData() { return responseData; } public void setResponseData(String responseData) { this.responseData = responseData; } public String getAssignedTo() { return assignedTo; } public void setAssignedTo(String assignedTo) { this.assignedTo = assignedTo; } } enum RequestType { ACCESS, RECTIFICATION, ERASURE, RESTRICTION, PORTABILITY, OBJECTION } enum RequestStatus { PENDING, IN_PROGRESS, COMPLETED, REJECTED, CANCELLED } @Entity @Table(name = "processing_activities") public class ProcessingActivity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank private String name; private String description; @NotBlank private String purpose; @ElementCollection @CollectionTable(name = "processing_categories", joinColumns = @JoinColumn(name = "activity_id")) @Enumerated(EnumType.STRING) private Set<DataCategory> dataCategories = new HashSet<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "data_subject_id") private DataSubject dataSubject; @Enumerated(EnumType.STRING) private LegalBasis legalBasis; private LocalDateTime processingStart; private LocalDateTime processingEnd; @ElementCollection @CollectionTable(name = "data_recipients", joinColumns = @JoinColumn(name = "activity_id")) private Set<String> recipients = new HashSet<>(); private boolean thirdCountryTransfer; private String safeguards; private LocalDateTime createdAt; private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } // Getters and setters // ... (similar to previous entities) } enum DataCategory { IDENTIFYING, CONTACT, FINANCIAL, HEALTH, BIOMETRIC, LOCATION, BEHAVIORAL, DEMOGRAPHIC } enum LegalBasis { CONSENT, CONTRACT, LEGAL_OBLIGATION, VITAL_INTEREST, PUBLIC_TASK, LEGITIMATE_INTEREST }

2. Consent Management Service

import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.*; @Service public class ConsentManagementService { private final DataSubjectRepository dataSubjectRepository; private final ConsentRecordRepository consentRecordRepository; public ConsentManagementService(DataSubjectRepository dataSubjectRepository, ConsentRecordRepository consentRecordRepository) { this.dataSubjectRepository = dataSubjectRepository; this.consentRecordRepository = consentRecordRepository; } @Transactional public ConsentRecord grantConsent(String subjectId, ConsentRequest request, String ipAddress, String userAgent) { DataSubject dataSubject = dataSubjectRepository.findBySubjectId(subjectId) .orElseGet(() -> createDataSubject(subjectId, request.getEmail())); // Revoke any existing consent for the same purpose consentRecordRepository.findByDataSubjectAndPurposeAndGranted(dataSubject, request.getPurpose(), true) .ifPresent(existing -> revokeConsent(existing.getId())); ConsentRecord consent = new ConsentRecord(); consent.setDataSubject(dataSubject); consent.setPurpose(request.getPurpose()); consent.setLegalBasis(request.getLegalBasis()); consent.setGranted(true); consent.setConsentText(request.getConsentText()); consent.setVersion(request.getVersion()); consent.setGrantedAt(LocalDateTime.now()); consent.setExpiresAt(request.getExpiresAt()); consent.setIpAddress(ipAddress); consent.setUserAgent(userAgent); return consentRecordRepository.save(consent); } @Transactional public void revokeConsent(Long consentId) { consentRecordRepository.findById(consentId).ifPresent(consent -> { consent.setGranted(false); consent.setRevokedAt(LocalDateTime.now()); consentRecordRepository.save(consent); }); } @Transactional public void revokeAllConsents(String subjectId) { DataSubject dataSubject = dataSubjectRepository.findBySubjectId(subjectId) .orElseThrow(() -> new RuntimeException("Data subject not found")); consentRecordRepository.findByDataSubjectAndGranted(dataSubject, true) .forEach(consent -> { consent.setGranted(false); consent.setRevokedAt(LocalDateTime.now()); consentRecordRepository.save(consent); }); } public boolean hasValidConsent(String subjectId, String purpose) { return dataSubjectRepository.findBySubjectId(subjectId) .flatMap(dataSubject -> consentRecordRepository.findByDataSubjectAndPurposeAndGranted( dataSubject, purpose, true)) .filter(consent -> consent.getExpiresAt() == null || consent.getExpiresAt().isAfter(LocalDateTime.now())) .isPresent(); } public List<ConsentRecord> getConsentHistory(String subjectId) { return dataSubjectRepository.findBySubjectId(subjectId) .map(dataSubject -> consentRecordRepository.findByDataSubject(dataSubject)) .orElse(Collections.emptyList()); } public ConsentAuditReport generateConsentAudit(String subjectId) { DataSubject dataSubject = dataSubjectRepository.findBySubjectId(subjectId) .orElseThrow(() -> new RuntimeException("Data subject not found")); List<ConsentRecord> consents = consentRecordRepository.findByDataSubject(dataSubject); ConsentAuditReport report = new ConsentAuditReport(); report.setDataSubject(dataSubject); report.setTotalConsents(consents.size()); report.setActiveConsents(consents.stream() .filter(ConsentRecord::isGranted) .filter(c -> c.getExpiresAt() == null || c.getExpiresAt().isAfter(LocalDateTime.now())) .count()); report.setConsentRecords(consents); report.setGeneratedAt(LocalDateTime.now()); return report; } private DataSubject createDataSubject(String subjectId, String email) { DataSubject dataSubject = new DataSubject(); dataSubject.setSubjectId(subjectId); dataSubject.setEmail(email); return dataSubjectRepository.save(dataSubject); } } class ConsentRequest { private String purpose; private String legalBasis; private String consentText; private String version; private LocalDateTime expiresAt; private String email; // Getters and setters public String getPurpose() { return purpose; } public void setPurpose(String purpose) { this.purpose = purpose; } public String getLegalBasis() { return legalBasis; } public void setLegalBasis(String legalBasis) { this.legalBasis = legalBasis; } public String getConsentText() { return consentText; } public void setConsentText(String consentText) { this.consentText = consentText; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public LocalDateTime getExpiresAt() { return expiresAt; } public void setExpiresAt(LocalDateTime expiresAt) { this.expiresAt = expiresAt; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } class ConsentAuditReport { private DataSubject dataSubject; private long totalConsents; private long activeConsents; private List<ConsentRecord> consentRecords; private LocalDateTime generatedAt; // Getters and setters public DataSubject getDataSubject() { return dataSubject; } public void setDataSubject(DataSubject dataSubject) { this.dataSubject = dataSubject; } public long getTotalConsents() { return totalConsents; } public void setTotalConsents(long totalConsents) { this.totalConsents = totalConsents; } public long getActiveConsents() { return activeConsents; } public void setActiveConsents(long activeConsents) { this.activeConsents = activeConsents; } public List<ConsentRecord> getConsentRecords() { return consentRecords; } public void setConsentRecords(List<ConsentRecord> consentRecords) { this.consentRecords = consentRecords; } public LocalDateTime getGeneratedAt() { return generatedAt; } public void setGeneratedAt(LocalDateTime generatedAt) { this.generatedAt = generatedAt; } }

3. Data Subject Rights Service

import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @Service public class DataSubjectRightsService { private final DataSubjectRepository dataSubjectRepository; private final DataAccessRequestRepository accessRequestRepository; private final ProcessingActivityRepository processingActivityRepository; private final DataErasureService dataErasureService; private final DataPortabilityService dataPortabilityService; public DataSubjectRightsService(DataSubjectRepository dataSubjectRepository, DataAccessRequestRepository accessRequestRepository, ProcessingActivityRepository processingActivityRepository, DataErasureService dataErasureService, DataPortabilityService dataPortabilityService) { this.dataSubjectRepository = dataSubjectRepository; this.accessRequestRepository = accessRequestRepository; this.processingActivityRepository = processingActivityRepository; this.dataErasureService = dataErasureService; this.dataPortabilityService = dataPortabilityService; } @Transactional public DataAccessRequest submitAccessRequest(String subjectId, RequestType type, String details) { DataSubject dataSubject = dataSubjectRepository.findBySubjectId(subjectId) .orElseGet(() -> createDataSubject(subjectId)); DataAccessRequest request = new DataAccessRequest(); request.setDataSubject(dataSubject); request.setType(type); request.setStatus(RequestStatus.PENDING); request.setRequestedAt(LocalDateTime.now()); request.setDueDate(LocalDateTime.now().plusDays(30)); // GDPR requires response within 30 days request.setRequestDetails(details); return accessRequestRepository.save(request); } @Transactional public DataAccessRequest processAccessRequest(Long requestId, String assignedTo) { DataAccessRequest request = accessRequestRepository.findById(requestId) .orElseThrow(() -> new RuntimeException("Request not found")); request.setStatus(RequestStatus.IN_PROGRESS); request.setAssignedTo(assignedTo); return accessRequestRepository.save(request); } @Transactional public DataAccessRequest completeAccessRequest(Long requestId, String responseData) { DataAccessRequest request = accessRequestRepository.findById(requestId) .orElseThrow(() -> new RuntimeException("Request not found")); request.setStatus(RequestStatus.COMPLETED); request.setCompletedAt(LocalDateTime.now()); request.setResponseData(responseData); return accessRequestRepository.save(request); } public String generateDataPortabilityReport(String subjectId) { DataSubject dataSubject = dataSubjectRepository.findBySubjectId(subjectId) .orElseThrow(() -> new RuntimeException("Data subject not found")); return dataPortabilityService.generatePortableData(dataSubject); } @Transactional public ErasureResult processRightToErasure(String subjectId, String reason) { DataSubject dataSubject = dataSubjectRepository.findBySubjectId(subjectId) .orElseThrow(() -> new RuntimeException("Data subject not found")); // Submit erasure request DataAccessRequest request = submitAccessRequest(subjectId, RequestType.ERASURE, reason); // Process erasure ErasureResult result = dataErasureService.erasePersonalData(dataSubject); // Update request status request.setStatus(RequestStatus.COMPLETED); request.setCompletedAt(LocalDateTime.now()); request.setResponseData("Erasure completed: " + result.getSummary()); accessRequestRepository.save(request); return result; } public DataSubjectRightsSummary getRightsSummary(String subjectId) { DataSubject dataSubject = dataSubjectRepository.findBySubjectId(subjectId) .orElseThrow(() -> new RuntimeException("Data subject not found")); List<DataAccessRequest> requests = accessRequestRepository.findByDataSubject(dataSubject); List<ProcessingActivity> activities = processingActivityRepository.findByDataSubject(dataSubject); DataSubjectRightsSummary summary = new DataSubjectRightsSummary(); summary.setDataSubject(dataSubject); summary.setTotalRequests(requests.size()); summary.setPendingRequests(requests.stream() .filter(r -> r.getStatus() == RequestStatus.PENDING) .count()); summary.setActiveProcessingActivities(activities.size()); summary.setLastRequest(requests.stream() .map(DataAccessRequest::getRequestedAt) .max(LocalDateTime::compareTo) .orElse(null)); return summary; } private DataSubject createDataSubject(String subjectId) { DataSubject dataSubject = new DataSubject(); dataSubject.setSubjectId(subjectId); return dataSubjectRepository.save(dataSubject); } } class DataSubjectRightsSummary { private DataSubject dataSubject; private long totalRequests; private long pendingRequests; private long activeProcessingActivities; private LocalDateTime lastRequest; // Getters and setters public DataSubject getDataSubject() { return dataSubject; } public void setDataSubject(DataSubject dataSubject) { this.dataSubject = dataSubject; } public long getTotalRequests() { return totalRequests; } public void setTotalRequests(long totalRequests) { this.totalRequests = totalRequests; } public long getPendingRequests() { return pendingRequests; } public void setPendingRequests(long pendingRequests) { this.pendingRequests = pendingRequests; } public long getActiveProcessingActivities() { return activeProcessingActivities; } public void setActiveProcessingActivities(long activeProcessingActivities) { this.activeProcessingActivities = activeProcessingActivities; } public LocalDateTime getLastRequest() { return lastRequest; } public void setLastRequest(LocalDateTime lastRequest) { this.lastRequest = lastRequest; } }

4. Data Erasure Service

import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; @Service public class DataErasureService { private final DataSubjectRepository dataSubjectRepository; private final ConsentRecordRepository consentRecordRepository; private final ProcessingActivityRepository processingActivityRepository; private final List<DataErasureHandler> erasureHandlers; public DataErasureService(DataSubjectRepository dataSubjectRepository, ConsentRecordRepository consentRecordRepository, ProcessingActivityRepository processingActivityRepository, List<DataErasureHandler> erasureHandlers) { this.dataSubjectRepository = dataSubjectRepository; this.consentRecordRepository = consentRecordRepository; this.processingActivityRepository = processingActivityRepository; this.erasureHandlers = erasureHandlers; } @Transactional public ErasureResult erasePersonalData(DataSubject dataSubject) { List<ErasureStep> steps = new ArrayList<>(); // Step 1: Anonymize personal data in the main entity steps.add(anonymizeDataSubject(dataSubject)); // Step 2: Revoke all consents steps.add(revokeAllConsents(dataSubject)); // Step 3: Stop processing activities steps.add(stopProcessingActivities(dataSubject)); // Step 4: Call custom erasure handlers steps.addAll(executeCustomErasureHandlers(dataSubject)); // Step 5: Create erasure audit trail steps.add(createErasureAudit(dataSubject)); return new ErasureResult(dataSubject.getSubjectId(), steps); } private ErasureStep anonymizeDataSubject(DataSubject dataSubject) { try { // Anonymize personal data dataSubject.setEmail(generateAnonymousIdentifier()); dataSubject.setPhone(null); if (dataSubject.getPersonalData() != null) { PersonalData personalData = dataSubject.getPersonalData(); personalData.setFirstName("ANONYMIZED"); personalData.setLastName("ANONYMIZED"); personalData.setDateOfBirth(null); personalData.setAddress(null); personalData.setCity(null); personalData.setPostalCode(null); } dataSubjectRepository.save(dataSubject); return new ErasureStep("ANONYMIZE_SUBJECT", "SUCCESS", "Personal data anonymized successfully"); } catch (Exception e) { return new ErasureStep("ANONYMIZE_SUBJECT", "FAILED", e.getMessage()); } } private ErasureStep revokeAllConsents(DataSubject dataSubject) { try { List<ConsentRecord> consents = consentRecordRepository.findByDataSubject(dataSubject); consents.forEach(consent -> { consent.setGranted(false); consent.setRevokedAt(LocalDateTime.now()); }); consentRecordRepository.saveAll(consents); return new ErasureStep("REVOKE_CONSENTS", "SUCCESS", "Revoked " + consents.size() + " consent records"); } catch (Exception e) { return new ErasureStep("REVOKE_CONSENTS", "FAILED", e.getMessage()); } } private ErasureStep stopProcessingActivities(DataSubject dataSubject) { try { List<ProcessingActivity> activities = processingActivityRepository.findByDataSubject(dataSubject); activities.forEach(activity -> activity.setProcessingEnd(LocalDateTime.now())); processingActivityRepository.saveAll(activities); return new ErasureStep("STOP_PROCESSING", "SUCCESS", "Stopped " + activities.size() + " processing activities"); } catch (Exception e) { return new ErasureStep("STOP_PROCESSING", "FAILED", e.getMessage()); } } private List<ErasureStep> executeCustomErasureHandlers(DataSubject dataSubject) { List<ErasureStep> steps = new ArrayList<>(); for (DataErasureHandler handler : erasureHandlers) { try { ErasureStep step = handler.eraseData(dataSubject); steps.add(step); } catch (Exception e) { steps.add(new ErasureStep(handler.getHandlerName(), "FAILED", e.getMessage())); } } return steps; } private ErasureStep createErasureAudit(DataSubject dataSubject) { // Create an audit record of the erasure // This could be stored in a separate audit table return new ErasureStep("CREATE_AUDIT", "SUCCESS", "Erasure audit trail created for subject: " + dataSubject.getSubjectId()); } private String generateAnonymousIdentifier() { return "anon_" + UUID.randomUUID().toString().substring(0, 8); } } interface DataErasureHandler { ErasureStep eraseData(DataSubject dataSubject); String getHandlerName(); } @Service class UserProfileErasureHandler implements DataErasureHandler { private final UserProfileRepository userProfileRepository; public UserProfileErasureHandler(UserProfileRepository userProfileRepository) { this.userProfileRepository = userProfileRepository; } @Override public ErasureStep eraseData(DataSubject dataSubject) { try { userProfileRepository.findBySubjectId(dataSubject.getSubjectId()) .ifPresent(profile -> { profile.setAvatarUrl(null); profile.setBiography(null); profile.setPreferences("{}"); userProfileRepository.save(profile); }); return new ErasureStep("ERASE_PROFILE", "SUCCESS", "User profile data erased"); } catch (Exception e) { return new ErasureStep("ERASE_PROFILE", "FAILED", e.getMessage()); } } @Override public String getHandlerName() { return "USER_PROFILE_ERASURE"; } } class ErasureResult { private String subjectId; private List<ErasureStep> steps; private LocalDateTime completedAt; private String summary; public ErasureResult(String subjectId, List<ErasureStep> steps) { this.subjectId = subjectId; this.steps = steps; this.completedAt = LocalDateTime.now(); this.summary = generateSummary(); } private String generateSummary() { long successful = steps.stream() .filter(step -> "SUCCESS".equals(step.getStatus())) .count(); return String.format("Erasure completed: %d/%d steps successful", successful, steps.size()); } // Getters public String getSubjectId() { return subjectId; } public List<ErasureStep> getSteps() { return steps; } public LocalDateTime getCompletedAt() { return completedAt; } public String getSummary() { return summary; } } class ErasureStep { private String operation; private String status; private String message; private LocalDateTime timestamp; public ErasureStep(String operation, String status, String message) { this.operation = operation; this.status = status; this.message = message; this.timestamp = LocalDateTime.now(); } // Getters public String getOperation() { return operation; } public String getStatus() { return status; } public String getMessage() { return message; } public LocalDateTime getTimestamp() { return timestamp; } }

5. Data Portability Service

import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import java.util.*; @Service public class DataPortabilityService { private final DataSubjectRepository dataSubjectRepository; private final ConsentRecordRepository consentRecordRepository; private final ProcessingActivityRepository processingActivityRepository; private final ObjectMapper objectMapper; private final List<DataPortabilityExporter> exporters; public DataPortabilityService(DataSubjectRepository dataSubjectRepository, ConsentRecordRepository consentRecordRepository, ProcessingActivityRepository processingActivityRepository, List<DataPortabilityExporter> exporters) { this.dataSubjectRepository = dataSubjectRepository; this.consentRecordRepository = consentRecordRepository; this.processingActivityRepository = processingActivityRepository; this.exporters = exporters; this.objectMapper = new ObjectMapper(); this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); } public String generatePortableData(DataSubject dataSubject) { try { PortableData portableData = new PortableData(); portableData.setSubjectId(dataSubject.getSubjectId()); portableData.setGeneratedAt(LocalDateTime.now()); portableData.setFormatVersion("1.0"); // Basic subject information (anonymized if needed) portableData.setBasicInformation(extractBasicInformation(dataSubject)); // Consent history portableData.setConsents(extractConsentHistory(dataSubject)); // Processing activities portableData.setProcessingActivities(extractProcessingActivities(dataSubject)); // Custom data from exporters portableData.setCustomData(extractCustomData(dataSubject)); return objectMapper.writeValueAsString(portableData); } catch (Exception e) { throw new RuntimeException("Failed to generate portable data: " + e.getMessage(), e); } } private BasicInformation extractBasicInformation(DataSubject dataSubject) { BasicInformation info = new BasicInformation(); info.setEmail(dataSubject.getEmail()); info.setCreatedAt(dataSubject.getCreatedAt()); info.setUpdatedAt(dataSubject.getUpdatedAt()); return info; } private List<PortableConsent> extractConsentHistory(DataSubject dataSubject) { return consentRecordRepository.findByDataSubject(dataSubject).stream() .map(consent -> { PortableConsent pc = new PortableConsent(); pc.setPurpose(consent.getPurpose()); pc.setLegalBasis(consent.getLegalBasis()); pc.setGranted(consent.isGranted()); pc.setGrantedAt(consent.getGrantedAt()); pc.setRevokedAt(consent.getRevokedAt()); pc.setExpiresAt(consent.getExpiresAt()); return pc; }) .collect(Collectors.toList()); } private List<PortableProcessingActivity> extractProcessingActivities(DataSubject dataSubject) { return processingActivityRepository.findByDataSubject(dataSubject).stream() .map(activity -> { PortableProcessingActivity ppa = new PortableProcessingActivity(); ppa.setName(activity.getName()); ppa.setPurpose(activity.getPurpose()); ppa.setLegalBasis(activity.getLegalBasis().name()); ppa.setProcessingStart(activity.getProcessingStart()); ppa.setProcessingEnd(activity.getProcessingEnd()); ppa.setDataCategories(activity.getDataCategories().stream() .map(Enum::name) .collect(Collectors.toList())); return ppa; }) .collect(Collectors.toList()); } private Map<String, Object> extractCustomData(DataSubject dataSubject) { Map<String, Object> customData = new HashMap<>(); for (DataPortabilityExporter exporter : exporters) { try { customData.put(exporter.getExporterName(), exporter.exportData(dataSubject)); } catch (Exception e) { customData.put(exporter.getExporterName(), Map.of("error", "Failed to export: " + e.getMessage())); } } return customData; } } interface DataPortabilityExporter { Object exportData(DataSubject dataSubject); String getExporterName(); } @Service class UserActivityExporter implements DataPortabilityExporter { private final UserActivityRepository userActivityRepository; public UserActivityExporter(UserActivityRepository userActivityRepository) { this.userActivityRepository = userActivityRepository; } @Override public Object exportData(DataSubject dataSubject) { List<UserActivity> activities = userActivityRepository.findBySubjectId(dataSubject.getSubjectId()); return activities.stream() .map(activity -> Map.of( "type", activity.getType(), "timestamp", activity.getTimestamp(), "details", activity.getDetails() )) .collect(Collectors.toList()); } @Override public String getExporterName() { return "user_activities"; } } class PortableData { private String subjectId; private LocalDateTime generatedAt; private String formatVersion; private BasicInformation basicInformation; private List<PortableConsent> consents; private List<PortableProcessingActivity> processingActivities; private Map<String, Object> customData; // Getters and setters public String getSubjectId() { return subjectId; } public void setSubjectId(String subjectId) { this.subjectId = subjectId; } public LocalDateTime getGeneratedAt() { return generatedAt; } public void setGeneratedAt(LocalDateTime generatedAt) { this.generatedAt = generatedAt; } public String getFormatVersion() { return formatVersion; } public void setFormatVersion(String formatVersion) { this.formatVersion = formatVersion; } public BasicInformation getBasicInformation() { return basicInformation; } public void setBasicInformation(BasicInformation basicInformation) { this.basicInformation = basicInformation; } public List<PortableConsent> getConsents() { return consents; } public void setConsents(List<PortableConsent> consents) { this.consents = consents; } public List<PortableProcessingActivity> getProcessingActivities() { return processingActivities; } public void setProcessingActivities(List<PortableProcessingActivity> processingActivities) { this.processingActivities = processingActivities; } public Map<String, Object> getCustomData() { return customData; } public void setCustomData(Map<String, Object> customData) { this.customData = customData; } } class BasicInformation { private String email; private LocalDateTime createdAt; private LocalDateTime updatedAt; // Getters and setters public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } } class PortableConsent { private String purpose; private String legalBasis; private boolean granted; private LocalDateTime grantedAt; private LocalDateTime revokedAt; private LocalDateTime expiresAt; // Getters and setters // ... (similar to above) } class PortableProcessingActivity { private String name; private String purpose; private String legalBasis; private LocalDateTime processingStart; private LocalDateTime processingEnd; private List<String> dataCategories; // Getters and setters // ... (similar to above) }

6. REST Controllers

import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; import org.springframework.http.HttpStatus; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import java.util.*; @RestController @RequestMapping("/api/gdpr") public class GDPRController { private final ConsentManagementService consentService; private final DataSubjectRightsService rightsService; public GDPRController(ConsentManagementService consentService, DataSubjectRightsService rightsService) { this.consentService = consentService; this.rightsService = rightsService; } @PostMapping("/consent") public ResponseEntity<ApiResponse<ConsentRecord>> grantConsent( @Valid @RequestBody ConsentRequest request, @RequestHeader("X-Subject-ID") String subjectId, HttpServletRequest httpRequest) { String ipAddress = httpRequest.getRemoteAddr(); String userAgent = httpRequest.getHeader("User-Agent"); ConsentRecord consent = consentService.grantConsent( subjectId, request, ipAddress, userAgent); return ResponseEntity.status(HttpStatus.CREATED) .body(new ApiResponse<>("Consent granted successfully", consent)); } @DeleteMapping("/consent/{consentId}") public ResponseEntity<ApiResponse<Void>> revokeConsent( @PathVariable Long consentId) { consentService.revokeConsent(consentId); return ResponseEntity.ok(new ApiResponse<>("Consent revoked successfully", null)); } @GetMapping("/consent/history") public ResponseEntity<ApiResponse<List<ConsentRecord>>> getConsentHistory( @RequestHeader("X-Subject-ID") String subjectId) { List<ConsentRecord> history = consentService.getConsentHistory(subjectId); return ResponseEntity.ok(new ApiResponse<>("Consent history retrieved", history)); } @PostMapping("/rights/request") public ResponseEntity<ApiResponse<DataAccessRequest>> submitRightsRequest( @RequestHeader("X-Subject-ID") String subjectId, @RequestParam RequestType type, @RequestBody(required = false) String details) { DataAccessRequest request = rightsService.submitAccessRequest(subjectId, type, details); return ResponseEntity.status(HttpStatus.CREATED) .body(new ApiResponse<>("Rights request submitted", request)); } @GetMapping("/rights/portability") public ResponseEntity<ApiResponse<String>> getPortableData( @RequestHeader("X-Subject-ID") String subjectId) { String portableData = rightsService.generateDataPortabilityReport(subjectId); return ResponseEntity.ok(new ApiResponse<>("Portable data generated", portableData)); } @PostMapping("/rights/erasure") public ResponseEntity<ApiResponse<ErasureResult>> requestErasure( @RequestHeader("X-Subject-ID") String subjectId, @RequestBody(required = false) String reason) { ErasureResult result = rightsService.processRightToErasure(subjectId, reason); return ResponseEntity.ok(new ApiResponse<>("Erasure request processed", result)); } @GetMapping("/rights/summary") public ResponseEntity<ApiResponse<DataSubjectRightsSummary>> getRightsSummary( @RequestHeader("X-Subject-ID") String subjectId) { DataSubjectRightsSummary summary = rightsService.getRightsSummary(subjectId); return ResponseEntity.ok(new ApiResponse<>("Rights summary retrieved", summary)); } @GetMapping("/consent/audit") public ResponseEntity<ApiResponse<ConsentAuditReport>> getConsentAudit( @RequestHeader("X-Subject-ID") String subjectId) { ConsentAuditReport audit = consentService.generateConsentAudit(subjectId); return ResponseEntity.ok(new ApiResponse<>("Consent audit generated", audit)); } } class ApiResponse<T> { private String message; private T data; private LocalDateTime timestamp; public ApiResponse(String message, T data) { this.message = message; this.data = data; this.timestamp = LocalDateTime.now(); } // Getters and setters public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } public LocalDateTime getTimestamp() { return timestamp; } public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } }

7. Security and Configuration

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(authz -> authz .requestMatchers("/api/gdpr/**").authenticated() .anyRequest().permitAll() ) .httpBasic(withDefaults()); return http.build(); } } @Configuration @ConfigurationProperties(prefix = "gdpr") public class GDPRConfig { private int defaultRetentionYears = 7; private int requestResponseDays = 30; private boolean requireExplicitConsent = true; private List<String> allowedDataCategories = Arrays.asList( "IDENTIFYING", "CONTACT", "DEMOGRAPHIC" ); // Getters and setters public int getDefaultRetentionYears() { return defaultRetentionYears; } public void setDefaultRetentionYears(int defaultRetentionYears) { this.defaultRetentionYears = defaultRetentionYears; } public int getRequestResponseDays() { return requestResponseDays; } public void setRequestResponseDays(int requestResponseDays) { this.requestResponseDays = requestResponseDays; } public boolean isRequireExplicitConsent() { return requireExplicitConsent; } public void setRequireExplicitConsent(boolean requireExplicitConsent) { this.requireExplicitConsent = requireExplicitConsent; } public List<String> getAllowedDataCategories() { return allowedDataCategories; } public void setAllowedDataCategories(List<String> allowedDataCategories) { this.allowedDataCategories = allowedDataCategories; } }

Key Features

  1. Consent Management: Track, manage, and audit user consents
  2. Data Subject Rights: Implement all GDPR rights (access, erasure, portability, etc.)
  3. Data Erasure: Secure and comprehensive data deletion
  4. Data Portability: Export user data in standardized formats
  5. Processing Activities: Record and manage data processing activities
  6. Audit Trail: Comprehensive logging of all GDPR-related activities
  7. Security: Secure API endpoints with proper authentication
  8. Extensibility: Plugin architecture for custom data handlers

This toolkit provides a solid foundation for GDPR compliance in Java applications, with extensible architecture to accommodate specific business requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper