SourceProvenanceService.java
package com.taxonomy.provenance.service;
import com.taxonomy.dto.RequirementSourceLinkDto;
import com.taxonomy.dto.SourceArtifactDto;
import com.taxonomy.model.LinkType;
import com.taxonomy.model.SourceType;
import com.taxonomy.provenance.model.RequirementSourceLink;
import com.taxonomy.provenance.model.SourceArtifact;
import com.taxonomy.provenance.model.SourceFragment;
import com.taxonomy.provenance.model.SourceVersion;
import com.taxonomy.provenance.repository.RequirementSourceLinkRepository;
import com.taxonomy.provenance.repository.SourceArtifactRepository;
import com.taxonomy.provenance.repository.SourceFragmentRepository;
import com.taxonomy.provenance.repository.SourceVersionRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/**
* Central service for managing source provenance. Every requirement should be
* traceable to its source via this service.
*/
@Service
public class SourceProvenanceService {
private static final Logger log = LoggerFactory.getLogger(SourceProvenanceService.class);
private final SourceArtifactRepository artifactRepo;
private final SourceVersionRepository versionRepo;
private final SourceFragmentRepository fragmentRepo;
private final RequirementSourceLinkRepository linkRepo;
public SourceProvenanceService(SourceArtifactRepository artifactRepo,
SourceVersionRepository versionRepo,
SourceFragmentRepository fragmentRepo,
RequirementSourceLinkRepository linkRepo) {
this.artifactRepo = artifactRepo;
this.versionRepo = versionRepo;
this.fragmentRepo = fragmentRepo;
this.linkRepo = linkRepo;
}
// ── Artifact operations ────────────────────────────────────────────────────
@Transactional
public SourceArtifact createArtifact(SourceType sourceType, String title) {
SourceArtifact artifact = new SourceArtifact(sourceType, title);
artifact = artifactRepo.save(artifact);
log.info("Created source artifact id={} type={} title=\"{}\"", artifact.getId(), sourceType, title);
return artifact;
}
@Transactional(readOnly = true)
public List<SourceArtifact> findArtifactsByType(SourceType sourceType) {
return artifactRepo.findBySourceType(sourceType);
}
@Transactional(readOnly = true)
public List<SourceArtifactDto> listAllArtifacts() {
return artifactRepo.findAll().stream().map(this::toDto).toList();
}
@Transactional(readOnly = true)
public Optional<SourceArtifact> findArtifactById(Long id) {
return artifactRepo.findById(id);
}
// ── Version operations ─────────────────────────────────────────────────────
@Transactional
public SourceVersion createVersion(SourceArtifact artifact, String mimeType, String contentHash) {
SourceVersion version = new SourceVersion(artifact);
version.setMimeType(mimeType);
version.setContentHash(contentHash);
return versionRepo.save(version);
}
@Transactional(readOnly = true)
public Optional<SourceVersion> findVersionById(Long id) {
return versionRepo.findById(id);
}
// ── Fragment operations ────────────────────────────────────────────────────
@Transactional
public SourceFragment createFragment(SourceVersion version, String fragmentText,
String sectionPath, Integer pageFrom) {
SourceFragment fragment = new SourceFragment(version, fragmentText);
fragment.setSectionPath(sectionPath);
fragment.setPageFrom(pageFrom);
return fragmentRepo.save(fragment);
}
// ── Link operations ────────────────────────────────────────────────────────
@Transactional
public RequirementSourceLink linkRequirement(String requirementId,
SourceArtifact artifact,
SourceVersion version,
SourceFragment fragment,
LinkType linkType) {
RequirementSourceLink link = new RequirementSourceLink(requirementId, artifact, linkType);
link.setSourceVersion(version);
link.setSourceFragment(fragment);
link = linkRepo.save(link);
log.debug("Linked requirement {} → artifact {} ({})", requirementId, artifact.getId(), linkType);
return link;
}
@Transactional(readOnly = true)
public List<RequirementSourceLinkDto> getLinksForRequirement(String requirementId) {
return linkRepo.findByRequirementId(requirementId).stream()
.map(this::toLinkDto)
.toList();
}
// ── DTO mapping ────────────────────────────────────────────────────────────
private SourceArtifactDto toDto(SourceArtifact entity) {
SourceArtifactDto dto = new SourceArtifactDto(entity.getSourceType(), entity.getTitle());
dto.setId(entity.getId());
dto.setCanonicalIdentifier(entity.getCanonicalIdentifier());
dto.setCanonicalUrl(entity.getCanonicalUrl());
dto.setOriginSystem(entity.getOriginSystem());
dto.setAuthor(entity.getAuthor());
dto.setDescription(entity.getDescription());
dto.setLanguage(entity.getLanguage());
dto.setCreatedAt(entity.getCreatedAt());
dto.setUpdatedAt(entity.getUpdatedAt());
return dto;
}
private RequirementSourceLinkDto toLinkDto(RequirementSourceLink entity) {
RequirementSourceLinkDto dto = new RequirementSourceLinkDto();
dto.setId(entity.getId());
dto.setRequirementId(entity.getRequirementId());
dto.setSourceArtifactId(entity.getSourceArtifact().getId());
dto.setLinkType(entity.getLinkType());
dto.setConfidence(entity.getConfidence());
dto.setNote(entity.getNote());
dto.setSourceTitle(entity.getSourceArtifact().getTitle());
dto.setSourceTypeName(entity.getSourceArtifact().getSourceType().name());
if (entity.getSourceVersion() != null) {
dto.setSourceVersionId(entity.getSourceVersion().getId());
}
if (entity.getSourceFragment() != null) {
dto.setSourceFragmentId(entity.getSourceFragment().getId());
}
return dto;
}
}