class TrustAnchorValidationService {

    public void validate(long trustAnchorId) {
        Optional<TrustAnchor> maybeTrustAnchor = storage.readTx(tx -> trustAnchors.get(tx, Key.of(trustAnchorId)));
        if (!maybeTrustAnchor.isPresent()) {
            log.error("Trust anchor {} doesn't exist.", trustAnchorId);
            return;
        }

        TrustAnchor trustAnchor = maybeTrustAnchor.get();
        log.info("trust anchor {} located at {} with subject public key info {}", trustAnchor.getName(), trustAnchor.getLocations(), trustAnchor.getSubjectPublicKeyInfo());

        TrustAnchorValidationRun validationRun = storage.readTx(tx -> {
            final Ref<TrustAnchor> trustAnchorRef = trustAnchors.makeRef(tx, Key.of(trustAnchorId));
            return new TrustAnchorValidationRun(trustAnchorRef, trustAnchor.getLocations().get(0));
        });

        final ValidationLocation trustAnchorValidationLocation = new ValidationLocation(validationRun.getTrustAnchorCertificateURI());
        ValidationResult validationResult = newValidationResult(trustAnchorValidationLocation);

        boolean updatedTrustAnchor = false;
        try {
            final Optional<Tuple2<URI, byte[]>> maybeTrustAnchorCertificate = fetchPreferredTrustAnchorCertificate(trustAnchor, validationResult);

            if (maybeTrustAnchorCertificate.isPresent()) {
                final Tuple2<URI, byte[]> res = maybeTrustAnchorCertificate.get();
                updatedTrustAnchor = readTrustAnchorFromLocation(res.v2, trustAnchor, res.v1, validationResult);
            } else {
                validationResult.error(
                        ErrorCodes.TRUST_ANCHOR_FETCH,
                        "any location",
                        String.format("None of the locations (%s) could be loaded.", Joiner.on(", ").join(trustAnchor.getLocations())));
                validationRun.setFailed();
            }

            if (validationResult.hasFailures()) {
                log.warn("Validation result for the TA {} has failures: {}", trustAnchor.getName(),
                        validationResult.getFailuresForAllLocations());
            }

            if (trustAnchor.getRsyncPrefetchUri() != null) {
                storage.writeTx0(tx -> {
                    final Ref<TrustAnchor> trustAnchorRef = trustAnchors.makeRef(tx, trustAnchor.key());
                    rpkiRepositories.register(tx, trustAnchorRef,
                            trustAnchor.getRsyncPrefetchUri(), RpkiRepository.Type.RSYNC_PREFETCH);
                });
            }

            validationRun.completeWith(validationResult);
            if (!validatedAtLeastOnce.contains(trustAnchor.getId()) || updatedTrustAnchor) {
                if (updatedTrustAnchor) {
                    storage.writeTx0(tx -> trustAnchors.update(tx, trustAnchor));
                }
                final Set<TrustAnchor> affectedTrustAnchors = Sets.newHashSet(trustAnchor);
                if (trustAnchor.getRsyncPrefetchUri() != null) {
                    storage.readTx(tx ->
                            rpkiRepositories.findByURI(tx, trustAnchor.getRsyncPrefetchUri()))
                            .ifPresent(r ->
                                    affectedTrustAnchors.addAll(repositoryValidationService.prefetchRepository(r)));
                }
                affectedTrustAnchors.forEach(validationScheduler::triggerCertificateTreeValidation);
            }
        } catch (CommandExecutionException | IOException e) {
            log.error("validation run for trust anchor {} failed", trustAnchor, e);
            validationRun.addCheck(new ValidationCheck(validationRun.getTrustAnchorCertificateURI(), ValidationCheck.Status.ERROR, ErrorCodes.UNHANDLED_EXCEPTION, e.toString()));
            validationRun.setFailed();
        } finally {
            validatedAtLeastOnce.add(trustAnchor.getId());
            storage.writeTx0(tx -> validationRuns.add(tx, validationRun));
        }
    }

}

class TrustAnchorValidationServiceTest  {

    private static final String DUMMY_RSYNC_URI;
    @Autowired
    private TrustAnchors trustAnchors;
    @Autowired
    private TrustAnchorValidationService subject;
    @Autowired
    private ValidationRuns validationRuns;
    @Autowired
    private TrustAnchorRetrievalService trustAnchorRetrievalService;

    @Test
    public void test_empty_file() throws IOException {

        TrustAnchor ta = createRipeNccTrustAnchor();
        wtx0(tx -> trustAnchors.add(tx, ta));

        ta.setLocations(ImmutableList.of(new ClassPathResource("empty-file.cer").getURI().toString()));
        wtx0(tx -> trustAnchors.update(tx, ta));
        subject.validate(ta.key().asLong());

        X509ResourceCertificate certificate = rtx(tx -> trustAnchors.get(tx, ta.key()).get().getCertificate());
        assertThat(certificate).isNull();

        Optional<TrustAnchorValidationRun> validationRun = rtx(tx -> validationRuns.findLatestCompletedForTrustAnchor(tx, ta));
        assertThat(validationRun).isPresent();

        List<ValidationCheck> validationChecks = validationRun.get().getValidationChecks();
        assertThat(validationChecks).hasSize(1);
        assertThat(validationChecks.get(0).getKey()).isEqualTo(ErrorCodes.REPOSITORY_OBJECT_MINIMUM_SIZE);
    }

}