ContentCanonicalizationMaintenanceService.java
package net.sasasin.sreader.service.canonicalization;
import net.sasasin.sreader.domain.ContentCanonicalizationPlan;
import net.sasasin.sreader.domain.ContentCanonicalizationResult;
import net.sasasin.sreader.repository.ContentCanonicalizationMaintenanceRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class ContentCanonicalizationMaintenanceService {
private static final Logger logger =
LoggerFactory.getLogger(ContentCanonicalizationMaintenanceService.class);
private final ContentCanonicalizationCandidateScanner candidateScanner;
private final ContentCanonicalizationPlanner planner;
private final ContentCanonicalizationExecutor executor;
private final ContentCanonicalizationFileCleaner fileCleaner;
ContentCanonicalizationMaintenanceService(
ContentCanonicalizationCandidateScanner candidateScanner,
ContentCanonicalizationPlanner planner,
ContentCanonicalizationExecutor executor,
ContentCanonicalizationFileCleaner fileCleaner) {
this.candidateScanner = candidateScanner;
this.planner = planner;
this.executor = executor;
this.fileCleaner = fileCleaner;
}
public ContentCanonicalizationResult canonicalize(Options options) {
ContentCanonicalizationResultAccumulator result =
new ContentCanonicalizationResultAccumulator();
ContentCanonicalizationCandidateScanner.Session scanner =
candidateScanner.start(options.host(), options.batchSize());
while (options.limit() == null || result.processedGroups() < options.limit()) {
ContentCanonicalizationCandidateScanner.Page page = scanner.next();
if (page.isFinished()) {
return result.snapshot();
}
result.addScannedRows(page.scannedRows());
for (ContentCanonicalizationCandidateScanner.GroupCandidate candidate : page.groups()) {
String normalized = candidate.normalizedUrl();
var group = candidate.group();
if (!planner.needsChange(group)) {
result.addUnchangedRows(group.members().size());
continue;
}
ContentCanonicalizationPlan plan = planner.plan(group);
result.recordPlannedGroup(plan);
if (options.apply()) {
try {
ContentCanonicalizationMaintenanceRepository.MergeCounts counts =
executor.execute(plan);
result.addMergeCounts(counts);
result.addFileSummary(fileCleaner.clean(plan));
} catch (RuntimeException e) {
logger.error("Could not canonicalize group {}", normalized, e);
result.recordFailedGroup();
}
}
if (options.limit() != null && result.processedGroups() >= options.limit()) {
return result.snapshot();
}
}
}
return result.snapshot();
}
public record Options(String host, int batchSize, Integer limit, boolean apply) {
public Options {
if (host != null && host.isBlank()) {
host = null;
}
if (batchSize <= 0) {
throw new IllegalArgumentException("batchSize must be positive");
}
if (limit != null && limit <= 0) {
throw new IllegalArgumentException("limit must be positive");
}
}
}
}