Skip to content

Add checksum option to DROID API#1341

Merged
MancunianSam merged 7 commits into
developfrom
add-checksum-option-to-droid-api
May 22, 2025
Merged

Add checksum option to DROID API#1341
MancunianSam merged 7 commits into
developfrom
add-checksum-option-to-droid-api

Conversation

@MancunianSam

Copy link
Copy Markdown
Collaborator

This allows you to pass in a list of hash algorithms and the API will
return the checksums for the file for that algorithm.

This allows you to pass in a list of hash algorithms and the API will
return the checksums for the file for that algorithm.
@MancunianSam MancunianSam changed the base branch from main to develop May 20, 2025 15:42
}

private <T> Map<HashAlgorithm, String> generateHashResults(T identifier, BiFunction<HashAlgorithm, T, String> hashFunction) {
return hashAlgorithms.stream().collect(Collectors.toMap(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential NPE if hashAlgorithm is not requested by the API user.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good spot. And the test was always passing in an empty list so I didn't notice. The builder will now return an empty list if hasAlgorithms isn't set and I've updated the test to pass null in to check that it works.

String droidVersion = ResourceBundle.getBundle("options").getString("version_no");
ContainerApi containerApi = new ContainerApi(droidCore, containerSignature);
return new DroidAPI(droidCore, containerApi.zipIdentifier(), containerApi.ole2Identifier(), containerApi.gzIdentifier(), containerVersion, droidCore.getSigFile().getVersion(), droidVersion, this.s3Client, this.httpClient, this.s3Region);
List<HashAlgorithm> hashAlgorithmsOrEmptyList = this.hashAlgorithms == null ? Collections.emptyList() : this.hashAlgorithms;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be my ignorance of Java, but on line 182, couldn't you just do

private List<HashAlgorithm> hashAlgorithms = Collections.emptyList(); or
private List<HashAlgorithm> hashAlgorithmsOrEmptyList = Collections.emptyList();?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, I've changed the builder so it calls private List<HashAlgorithm> hashAlgorithms = Collections.emptyList();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would still need the null protection in case someone directly uses Builder and passes in a null. Might be easier to null check in public DroidAPIBuilder hashAlgorithms(final List<HashAlgorithm> hashAlgorithms)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did think that but then I thought, if you're explicitly passing null into a builder then you deserve what happens to you. That being said, I've added a null check in the builder method.

));
}

private String getFileHash(HashAlgorithm hashAlgorithm, Path path) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the FileInputStream staying open here?
Might be easier to use try-with-resource so it closes automatically

        try (InputStream fs = new FileInputStream(path.toFile())){
            return getHash(hashAlgorithm, fs);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I've changed it.

private String getS3Hash(HashAlgorithm algorithm, S3Uri s3Uri) {
String key = s3Uri.key().orElseThrow(() -> new RuntimeException("Key not found in uri " + s3Uri.uri()));
String bucket = s3Uri.bucket().orElseThrow(() -> new RuntimeException("Bucket not found in uri " + s3Uri.uri()));
ResponseInputStream<GetObjectResponse> responseInputStream = s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(key).build());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to above, try-with-resource to close the ResponseInputStream

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this one as well.

@sparkhi sparkhi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments inline. Looks good. Do we need to convey the ApiResult vs APIResult breaking change to TDR in advance.

... and may I tempt you to update this page too -> https://github.com/digital-preservation/droid/wiki/How-to-use-DROID-internal-API (sorry!!)

@sparkhi

sparkhi commented May 21, 2025

Copy link
Copy Markdown
Collaborator

A couple of comments inline. Looks good. Do we need to convey the ApiResult vs APIResult breaking change to TDR in advance.

... and may I tempt you to update this page too -> https://github.com/digital-preservation/droid/wiki/How-to-use-DROID-internal-API (sorry!!)

I had another look at the "how to" page and it has been out of date for a while, feel free to ignore it for this PR.

}

private ApiResult createApiResult(IdentificationResult result, String extension, boolean extensionMismatch, URI uri) {
private IdentificationResult createIdentificationResult(uk.gov.nationalarchives.droid.core.interfaces.IdentificationResult result, String extension, boolean extensionMismatch, URI uri) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just use IdentificationResult or interfaces.IdentificationResult?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not with Java I don't think. This is a different class with the same name as the record I created. I could rename the record but I don't really want to. I've passed in the three parameters I need instead of using this class.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I've changed my mind, I've renamed the record APIIdentificationResult so this doesn't need the package qualifier now.

List<String> containerPuids = Arrays.asList(ZIP_PUID, OLE2_PUID, GZIP_PUID);
return binaryResult.getResults().stream()
.map(IdentificationResult::getPuid)
.map(uk.gov.nationalarchives.droid.core.interfaces.IdentificationResult::getPuid)

@techncl techncl May 21, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just use IdentificationResult or interfaces.IdentificationResult?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, I've renamed the record so this can be used unqualified.

return uri;
}
public enum HashAlgorithm {
/** MD5. **/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these comments necessary?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically they are because of the checkstyle rules but I agree that this is daft so I've disabled that rule for this class.

@MancunianSam MancunianSam merged commit a8ca8f1 into develop May 22, 2025
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants