diff --git a/droid-api/pom.xml b/droid-api/pom.xml index c273e58aa..a12ef0619 100644 --- a/droid-api/pom.xml +++ b/droid-api/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-api/src/main/java/uk/gov/nationalarchives/droid/internal/api/DroidAPI.java b/droid-api/src/main/java/uk/gov/nationalarchives/droid/internal/api/DroidAPI.java index bbaca30ab..17fb34471 100644 --- a/droid-api/src/main/java/uk/gov/nationalarchives/droid/internal/api/DroidAPI.java +++ b/droid-api/src/main/java/uk/gov/nationalarchives/droid/internal/api/DroidAPI.java @@ -92,6 +92,7 @@ public final class DroidAPI implements AutoCloseable { private static final String S3_SCHEME = "s3"; private static final String GZIP_PUID = "x-fmt/266"; private static final long DEFAULT_MAX_BYTES_TO_SCAN = -1; + private static final int DEFAULT_WINDOW_SIZE = 4 * 1024 * 1024; private static final AtomicLong ID_GENERATOR = new AtomicLong(); @@ -360,8 +361,8 @@ private List submitS3Identification(final URI uri, String extension) Map hashResults = generateHashResults(s3Uri, this::getS3Hash); final RequestIdentifier id = getRequestIdentifier(s3Uri.uri()); - RequestMetaData metaData = new RequestMetaData(s3Object.size(), s3Object.lastModified().getEpochSecond(), s3Uri.uri().toString()); - try (final S3IdentificationRequest request = new S3IdentificationRequest(metaData, id, s3Client)) { + RequestMetaData metaData = new RequestMetaData(s3Object.size(), s3Object.lastModified().toEpochMilli(), s3Uri.uri().toString()); + try (final S3IdentificationRequest request = new S3IdentificationRequest(metaData, id, s3Client, DEFAULT_WINDOW_SIZE)) { request.setExtension(extension); request.open(s3Uri); apiResults.add(new APIResult(getIdentificationResults(request), hashResults)); diff --git a/droid-api/src/test/java/uk/gov/nationalarchives/droid/internal/api/DroidAPITestUtils.java b/droid-api/src/test/java/uk/gov/nationalarchives/droid/internal/api/DroidAPITestUtils.java index c64d15ba0..dbd08e5c9 100644 --- a/droid-api/src/test/java/uk/gov/nationalarchives/droid/internal/api/DroidAPITestUtils.java +++ b/droid-api/src/test/java/uk/gov/nationalarchives/droid/internal/api/DroidAPITestUtils.java @@ -169,16 +169,6 @@ static HttpServer createS3Server() throws IOException { OutputStream responseBody = exchange.getResponseBody(); responseBody.write(response.getBytes()); responseBody.close(); - } else if (exchange.getRequestMethod().equals("HEAD")) { - String fullPath = exchange.getRequestURI().getPath().substring(1); - Path filePath = getFilePathFromUriPath(fullPath.substring(fullPath.indexOf("/"))); - long size = Files.size(filePath); - exchange.getResponseHeaders().add("Content-Length", Long.toString(size)); - exchange.getResponseHeaders().add("Last-Modified", "Mon, 03 Mar 2025 17:29:48 GMT"); - exchange.sendResponseHeaders(200, -1); - OutputStream responseBody = exchange.getResponseBody(); - responseBody.write("".getBytes()); - responseBody.close(); } else if (exchange.getRequestMethod().equals("GET")) { String fullPath = exchange.getRequestURI().getPath().substring(1); Path filePath = getFilePathFromUriPath(fullPath.substring(fullPath.indexOf("/"))); diff --git a/droid-binary/pom.xml b/droid-binary/pom.xml index 35eaae08d..1cdb5b80e 100644 --- a/droid-binary/pom.xml +++ b/droid-binary/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-build-tools/pom.xml b/droid-build-tools/pom.xml index f0c7ff466..92b07bcf1 100644 --- a/droid-build-tools/pom.xml +++ b/droid-build-tools/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-command-line/pom.xml b/droid-command-line/pom.xml index 43abd9336..d5c9ba79a 100644 --- a/droid-command-line/pom.xml +++ b/droid-command-line/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-container/pom.xml b/droid-container/pom.xml index bc374114f..a55fd49b5 100644 --- a/droid-container/pom.xml +++ b/droid-container/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-core-interfaces/pom.xml b/droid-core-interfaces/pom.xml index 922268601..7d76f2c6b 100644 --- a/droid-core-interfaces/pom.xml +++ b/droid-core-interfaces/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/http/S3ClientFactory.java b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/http/S3ClientFactory.java index a6710705b..3564c4131 100644 --- a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/http/S3ClientFactory.java +++ b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/http/S3ClientFactory.java @@ -47,12 +47,15 @@ public class S3ClientFactory implements ProxySubscriber { private S3Client s3Client; - private final Region region; + private Region region; - public S3ClientFactory(ProxySettings proxySettings) { + public S3ClientFactory() { + } + + public void init(ProxySettings proxySettings) { + this.region = DefaultAwsRegionProviderChain.builder().build().getRegion(); proxySettings.addProxySubscriber(this); setS3Client(proxySettings); - this.region = DefaultAwsRegionProviderChain.builder().build().getRegion(); } @Override diff --git a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequest.java b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequest.java index 5a41f856b..b80233e37 100644 --- a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequest.java +++ b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequest.java @@ -51,25 +51,27 @@ public class S3IdentificationRequest implements IdentificationRequest { private WindowReader s3Reader; private final RequestIdentifier identifier; private final RequestMetaData requestMetaData; + private final int windowSize; private final S3Client s3client; private final S3Utils.S3ObjectMetadata s3ObjectMetadata; private String extension; - public S3IdentificationRequest(final RequestMetaData requestMetaData, final RequestIdentifier identifier, final S3Client s3Client) { + public S3IdentificationRequest(final RequestMetaData requestMetaData, final RequestIdentifier identifier, final S3Client s3Client, final int windowSize) { this.identifier = identifier; this.s3client = s3Client; this.requestMetaData = requestMetaData; + this.windowSize = windowSize; S3Utils s3Utils = new S3Utils(s3Client); - this.s3ObjectMetadata = s3Utils.getS3ObjectMetadata(identifier.getUri()); + this.s3ObjectMetadata = s3Utils.getS3ObjectMetadata(identifier.getUri(), requestMetaData); this.s3Reader = buildWindowReader(); } private WindowReader buildWindowReader() { final WindowCache cache = new TopAndTailFixedLengthCache(this.s3ObjectMetadata.contentLength(), TOP_TAIL_BUFFER_CAPACITY); - return new S3WindowReader(cache, s3ObjectMetadata, s3client); + return new S3WindowReader(cache, s3ObjectMetadata, s3client, windowSize); } /** @@ -158,4 +160,12 @@ public byte getByte(long position) throws IOException { public WindowReader getWindowReader() { return this.s3Reader; } + + /** + * Gets the window size. + * @return the window size + */ + public int getWindowSize() { + return windowSize; + } } diff --git a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3Utils.java b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3Utils.java index 3c2414257..cada1fbae 100644 --- a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3Utils.java +++ b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3Utils.java @@ -63,26 +63,16 @@ public record S3ObjectMetadata(String bucket, Optional key, S3Uri uri, L public record S3ObjectList(String bucket, Iterable contents) {} - public S3ObjectMetadata getS3ObjectMetadata(final URI uri) { + public S3ObjectMetadata getS3ObjectMetadata(final URI uri, RequestMetaData requestMetaData) { S3Uri s3Uri = S3Utilities.builder().region(region).build().parseUri(uri); - return getS3ObjectMetadata(s3Uri); + return getS3ObjectMetadata(s3Uri, requestMetaData); } - public S3ObjectMetadata getS3ObjectMetadata(final S3Uri s3Uri) { + public S3ObjectMetadata getS3ObjectMetadata(final S3Uri s3Uri, RequestMetaData requestMetaData) { String bucket = s3Uri.bucket().orElseThrow(() -> new RuntimeException(BUCKET_NOT_FOUND + s3Uri)); Optional key = s3Uri.key(); - long contentLength = 0L; - long lastModified = 0L; - if (key.isPresent()) { - try { - HeadObjectRequest headObjectRequest = HeadObjectRequest.builder().bucket(bucket).key(key.get()).build(); - HeadObjectResponse headObjectResponse = this.s3Client.headObject(headObjectRequest); - contentLength = headObjectResponse.contentLength(); - lastModified = headObjectResponse.lastModified().getEpochSecond(); - } catch (NoSuchKeyException ignored) {} - } - return new S3ObjectMetadata(bucket, key, s3Uri, contentLength, lastModified); + return new S3ObjectMetadata(bucket, key, s3Uri, requestMetaData.getSize(), requestMetaData.getTime()); } public S3ObjectList listObjects(final URI uri) { diff --git a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReader.java b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReader.java index b7f974e99..36bc44c0d 100644 --- a/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReader.java +++ b/droid-core-interfaces/src/main/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReader.java @@ -49,16 +49,14 @@ public class S3WindowReader extends AbstractReader implements SoftWindowRecovery private static final int BUFFER_LENGTH = 8192; - private static final int WINDOW_SIZE = 4 * 1024 * 1024; - private final S3Utils.S3ObjectMetadata s3ObjectMetadata; private final S3Client s3Client; private final Long length; - public S3WindowReader(WindowCache cache, S3Utils.S3ObjectMetadata s3ObjectMetadata, S3Client s3Client) { - super(WINDOW_SIZE, cache); + public S3WindowReader(WindowCache cache, S3Utils.S3ObjectMetadata s3ObjectMetadata, S3Client s3Client, int windowSize) { + super(windowSize, cache); this.s3Client = s3Client; this.length = s3ObjectMetadata.contentLength(); this.s3ObjectMetadata = s3ObjectMetadata; diff --git a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequestTest.java b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequestTest.java index 3a7dc5f9d..605195c4b 100644 --- a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequestTest.java +++ b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3IdentificationRequestTest.java @@ -33,17 +33,12 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.exception.SdkException; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3Uri; import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.HeadObjectRequest; -import software.amazon.awssdk.services.s3.model.HeadObjectResponse; import uk.gov.nationalarchives.droid.core.interfaces.RequestIdentifier; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; @@ -63,11 +58,10 @@ public void testS3IdentificationRequestGetsFirstWindowOnCreation() throws IOExce request.open(s3Uri); ArgumentCaptor getObjectRequestCaptor = ArgumentCaptor.forClass(GetObjectRequest.class); - verify(mockS3Client, times(1)).headObject(any(HeadObjectRequest.class)); verify(mockS3Client, times(1)).getObject(getObjectRequestCaptor.capture()); GetObjectRequest getRequestValue = getObjectRequestCaptor.getValue(); - assertEquals(getRequestValue.range(), "bytes=0-4194303"); + assertEquals(getRequestValue.range(), "bytes=0-1023"); } @Test @@ -78,7 +72,7 @@ public void testS3IdentificationRequestHasCorrectAttributes() throws IOException request.open(s3Uri); - assertEquals(request.size(), 1); + assertEquals(request.size(), 2); assertEquals(request.getFileName(), "entry.txt"); assertEquals(request.getExtension(), "txt"); } @@ -126,18 +120,11 @@ public void testErrorIfS3GetObjectCallsFail() { assertThrows(SdkException.class, () -> request.open(s3Uri)); } - @Test - public void testErrorIfS3HeadObjectCallsFail() { - S3Client mockS3Client = mock(S3Client.class); - when(mockS3Client.headObject(any(HeadObjectRequest.class))).thenThrow(SdkException.class); - assertThrows(SdkException.class, () -> createRequest(mockS3Client)); - } - private S3IdentificationRequest createRequest(S3Client mockS3Client) { RequestMetaData requestMetaData = new RequestMetaData(2L, 1L, "entry.txt"); URI uri = URI.create("s3://bucket/test"); RequestIdentifier requestIdentifier = new RequestIdentifier(uri); - return new S3IdentificationRequest(requestMetaData, requestIdentifier, mockS3Client); + return new S3IdentificationRequest(requestMetaData, requestIdentifier, mockS3Client, 1024); } } diff --git a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3TestUtils.java b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3TestUtils.java index 94a3a1823..574d9d569 100644 --- a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3TestUtils.java +++ b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3TestUtils.java @@ -35,11 +35,8 @@ import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.HeadObjectRequest; -import software.amazon.awssdk.services.s3.model.HeadObjectResponse; import java.io.ByteArrayInputStream; -import java.time.Instant; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -49,10 +46,7 @@ public class S3TestUtils { static S3Client mockS3Client() { S3Client mockS3Client = mock(S3Client.class); - HeadObjectResponse response = HeadObjectResponse.builder().contentLength(1L).lastModified(Instant.ofEpochSecond(1)).build(); GetObjectResponse getObjectResponse = GetObjectResponse.builder().build(); - ResponseInputStream responseInputStream = new ResponseInputStream<>(getObjectResponse, new ByteArrayInputStream("test".getBytes())); - when(mockS3Client.headObject(any(HeadObjectRequest.class))).thenReturn(response); when(mockS3Client.getObject(any(GetObjectRequest.class))).thenAnswer(invocation -> { GetObjectRequest argument = invocation.getArgument(0, GetObjectRequest.class); String range = argument.range(); diff --git a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3UtilsTest.java b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3UtilsTest.java index 8442c7baf..3fd125536 100644 --- a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3UtilsTest.java +++ b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3UtilsTest.java @@ -57,9 +57,10 @@ public void getObjectMetadataReturnsCorrectMetadata() { S3Client s3Client = mockS3Client(); S3Utils s3Utils = new S3Utils(s3Client, Region.EU_WEST_2); URI uri = URI.create("s3://bucket/key"); - S3Utils.S3ObjectMetadata s3ObjectMetadataFromUri = s3Utils.getS3ObjectMetadata(uri); + RequestMetaData requestMetaData = new RequestMetaData(1L, 1L, "key"); + S3Utils.S3ObjectMetadata s3ObjectMetadataFromUri = s3Utils.getS3ObjectMetadata(uri, requestMetaData); S3Uri s3Uri = S3Uri.builder().uri(uri).bucket("bucket").key("key").build(); - S3Utils.S3ObjectMetadata s3ObjectMetadataFromS3Uri = s3Utils.getS3ObjectMetadata(s3Uri); + S3Utils.S3ObjectMetadata s3ObjectMetadataFromS3Uri = s3Utils.getS3ObjectMetadata(s3Uri, requestMetaData); for (S3Utils.S3ObjectMetadata s3ObjectMetadata: List.of(s3ObjectMetadataFromUri, s3ObjectMetadataFromS3Uri)) { assertTrue(s3ObjectMetadata.key().isPresent()); diff --git a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReaderTest.java b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReaderTest.java index 6b10f3603..433b4dd7d 100644 --- a/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReaderTest.java +++ b/droid-core-interfaces/src/test/java/uk/gov/nationalarchives/droid/core/interfaces/resource/S3WindowReaderTest.java @@ -61,7 +61,7 @@ public void testWindowReaderReturnsExpectedWindow() throws Exception { S3Client s3Client = mockS3Client(); S3Uri s3Uri = S3Uri.builder().uri(URI.create("s3://bucket/key")).build(); S3Utils.S3ObjectMetadata s3ObjectMetadata = new S3Utils.S3ObjectMetadata("bucket", Optional.of("key"), s3Uri, 4L, 1L); - S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client); + S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client, 1); byte[] testResponse = "test".getBytes(); for (int i = 0; i < testResponse.length; i++) { @@ -79,12 +79,12 @@ public void testWindowReaderReturnsExpectedWindowForLargeFile() throws Exception S3Client s3Client = mockS3Client(); S3Uri s3Uri = S3Uri.builder().uri(URI.create("s3://bucket/key")).build(); S3Utils.S3ObjectMetadata s3ObjectMetadata = new S3Utils.S3ObjectMetadata("bucket", Optional.of("key"), s3Uri, 4L, 1L); - S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client); + S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client, 10); s3WindowReader.createWindow(0); Collection invocations = Mockito.mockingDetails(s3Client).getInvocations(); GetObjectRequest getObjectRequest = (GetObjectRequest)invocations.stream().toList().getFirst().getArguments()[0]; - assertEquals(getObjectRequest.range(), "bytes=0-" + ((4 * 1024 * 1024) - 1)); + assertEquals(getObjectRequest.range(), "bytes=0-" + 9); } @Test @@ -93,7 +93,7 @@ public void testWindowReaderReturnsNullIfPositionLessThanZero() throws Exception S3Client s3Client = mock(S3Client.class); S3Uri s3Uri = S3Uri.builder().uri(URI.create("s3://bucket/key")).build(); S3Utils.S3ObjectMetadata s3ObjectMetadata = new S3Utils.S3ObjectMetadata("bucket", Optional.of("key"), s3Uri, 1L, 1L); - S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client); + S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client, 1); assertNull(s3WindowReader.createWindow(-1)); } @@ -104,7 +104,7 @@ public void testWindowReaderReturnsNullIfPositionGreaterOrEqualToLength() throws S3Client s3Client = mockS3Client(); S3Uri s3Uri = S3Uri.builder().uri(URI.create("s3://bucket/key")).build(); S3Utils.S3ObjectMetadata s3ObjectMetadata = new S3Utils.S3ObjectMetadata("bucket", Optional.of("key"), s3Uri, 4L, 1L); - S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client); + S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client, 1); assertNotNull(s3WindowReader.createWindow(3)); assertNull(s3WindowReader.createWindow(4)); @@ -119,7 +119,7 @@ public void testWindowReaderReturnsErrorOnS3Failure() throws Exception { when(s3Client.getObject(any(GetObjectRequest.class))).thenThrow(S3Exception.builder().message("Error contacting s3").build()); S3Uri s3Uri = S3Uri.builder().uri(URI.create("s3://bucket/key")).build(); S3Utils.S3ObjectMetadata s3ObjectMetadata = new S3Utils.S3ObjectMetadata("bucket", Optional.of("key"), s3Uri, 1L, 1L); - S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client); + S3WindowReader s3WindowReader = new S3WindowReader(windowCache, s3ObjectMetadata, s3Client, 1); assertThrows(S3Exception.class, () -> s3WindowReader.createWindow(0)); } diff --git a/droid-core/pom.xml b/droid-core/pom.xml index ad0526997..4f1f8ad12 100644 --- a/droid-core/pom.xml +++ b/droid-core/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent @@ -114,12 +114,6 @@ ${aws.version} test - - software.amazon.awssdk - aws-core - ${aws.version} - test - software.amazon.awssdk regions diff --git a/droid-core/src/main/java/uk/gov/nationalarchives/droid/core/signature/droid6/SubSequence.java b/droid-core/src/main/java/uk/gov/nationalarchives/droid/core/signature/droid6/SubSequence.java index cd5f65718..bbf708d89 100644 --- a/droid-core/src/main/java/uk/gov/nationalarchives/droid/core/signature/droid6/SubSequence.java +++ b/droid-core/src/main/java/uk/gov/nationalarchives/droid/core/signature/droid6/SubSequence.java @@ -1406,12 +1406,17 @@ private long[] bytePosForRightFragments(final WindowReader bytes, final long lef //Add the newly found fragment instance to the top of the stack and reset the loop // position to resume checking for further fragments from that point. tempEndPos[numEndPos] = tempFragEnd + searchDirection; + //CHECKSTYLE:OFF : Quite legitimate to modify control variables here, as we're + // reverting to an earlier fragment! + iFragPos = lastGoodFragRef.getFragmentSignaturePosition(); + iAlt = lastGoodFragRef.getAlternativeFragmentNumber(); + //CHECKSTYLE:ON //Get the offset of this new instance of the current fragment from the previous //fragment, or main sequence if this is the first fragment. long newOffSetFoundFromPreviousMatch = tempEndPos[numEndPos] - lastGoodFragRef.getPositionInFile() + lastGoodFragRef.getOffsetFound(); FragmentHit fragmentHit = - new FragmentHit(lastGoodFragRef.getFragmentSignaturePosition(), lastGoodFragRef.getAlternativeFragmentNumber(), tempEndPos[numEndPos], + new FragmentHit(iFragPos, iAlt, tempEndPos[numEndPos], newOffSetFoundFromPreviousMatch); fragmentHits.push(fragmentHit); numEndPos += 1; diff --git a/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/SkeletonSuiteTest.java b/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/SkeletonSuiteTest.java index df06f209a..6bbcd4d1c 100644 --- a/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/SkeletonSuiteTest.java +++ b/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/SkeletonSuiteTest.java @@ -195,7 +195,7 @@ public static Stream> identificationRequests() { fileSystemIdentificationRequest.open(skeletonPath); builder.add(fileSystemIdentificationRequest); - S3IdentificationRequest s3IdentificationRequest = new S3IdentificationRequest(metaData, s3Identifier, new TestS3Client()); + S3IdentificationRequest s3IdentificationRequest = new S3IdentificationRequest(metaData, s3Identifier, new TestS3Client(), 65536); s3IdentificationRequest.open(s3Uri); builder.add(s3IdentificationRequest); diff --git a/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/TestS3Client.java b/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/TestS3Client.java index 31137b776..4acdd9229 100644 --- a/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/TestS3Client.java +++ b/droid-core/src/test/java/uk/gov/nationalarchives/droid/core/TestS3Client.java @@ -57,13 +57,6 @@ public ResponseInputStream getObject(GetObjectRequest getObje } } - @Override - public HeadObjectResponse headObject(HeadObjectRequest headObjectRequest) throws NoSuchKeyException, AwsServiceException, - SdkClientException { - long size = FileUtils.sizeOf(new File(headObjectRequest.key())); - return HeadObjectResponse.builder().contentLength(size).lastModified(Instant.EPOCH).build(); - } - @Override public void close() {} diff --git a/droid-export-interfaces/pom.xml b/droid-export-interfaces/pom.xml index 316497baf..265d057cd 100644 --- a/droid-export-interfaces/pom.xml +++ b/droid-export-interfaces/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-export/pom.xml b/droid-export/pom.xml index fc5d5efe2..db5aa5e67 100644 --- a/droid-export/pom.xml +++ b/droid-export/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-help/pom.xml b/droid-help/pom.xml index 66fd831f5..dcc9d2026 100644 --- a/droid-help/pom.xml +++ b/droid-help/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-parent/pom.xml b/droid-parent/pom.xml index 5d2922804..7a893bd3b 100644 --- a/droid-parent/pom.xml +++ b/droid-parent/pom.xml @@ -4,7 +4,7 @@ uk.gov.nationalarchives droid-parent - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT pom droid-parent @@ -108,7 +108,7 @@ 2.0.17 2.25.2 10.21.2 - 2.34.8 + 2.35.0 6.0.0 2.20.0 diff --git a/droid-report-interfaces/pom.xml b/droid-report-interfaces/pom.xml index deb4295c3..b7cf10441 100644 --- a/droid-report-interfaces/pom.xml +++ b/droid-report-interfaces/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-report/pom.xml b/droid-report/pom.xml index d9f1e0593..88dd493a5 100644 --- a/droid-report/pom.xml +++ b/droid-report/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-results/pom.xml b/droid-results/pom.xml index 246ea7a95..4efb22330 100644 --- a/droid-results/pom.xml +++ b/droid-results/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent @@ -122,6 +122,7 @@ software.amazon.nio.s3:aws-java-nio-spi-for-s3:jar org.apache.derby:derby org.apache.httpcomponents:httpclient:jar + org.eclipse.angus:angus-mail:jar org.slf4j:slf4j-api @@ -203,6 +204,11 @@ jakarta.xml.bind-api 4.0.4 + + org.eclipse.angus + angus-mail + 2.0.3 + jakarta.xml.ws jakarta.xml.ws-api diff --git a/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3EventHandler.java b/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3EventHandler.java index 4da6cb7c4..26cbbf974 100644 --- a/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3EventHandler.java +++ b/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3EventHandler.java @@ -44,35 +44,43 @@ import uk.gov.nationalarchives.droid.core.interfaces.http.S3ClientFactory; import uk.gov.nationalarchives.droid.core.interfaces.resource.RequestMetaData; import uk.gov.nationalarchives.droid.core.interfaces.resource.S3IdentificationRequest; -import uk.gov.nationalarchives.droid.core.interfaces.resource.S3Utils; import uk.gov.nationalarchives.droid.profile.AbstractProfileResource; import uk.gov.nationalarchives.droid.profile.throttle.SubmissionThrottle; public class S3EventHandler { + private static final int DEFAULT_WINDOW_SIZE = 4 * 1024 * 1024; + private AsynchDroid droidCore; private SubmissionThrottle submissionThrottle; private ResultHandler resultHandler; private DroidGlobalConfig config; + private S3ClientFactory s3ClientFactory; - public S3EventHandler(final AsynchDroid droidCore, SubmissionThrottle submissionThrottle, ResultHandler resultHandler, DroidGlobalConfig config) { + public S3EventHandler(final AsynchDroid droidCore, SubmissionThrottle submissionThrottle, ResultHandler resultHandler, DroidGlobalConfig config, S3ClientFactory s3ClientFactory) { this.droidCore = droidCore; this.submissionThrottle = submissionThrottle; this.resultHandler = resultHandler; this.config = config; + this.s3ClientFactory = s3ClientFactory; } public void onS3Event(AbstractProfileResource resource, ResourceId parentResource) { S3Client s3Client = getS3Client(resource); - S3Utils s3Utils = new S3Utils(s3Client); - S3Utils.S3ObjectMetadata s3ObjectMetadata = s3Utils.getS3ObjectMetadata(resource.getUri()); - RequestMetaData metaData = new RequestMetaData(s3ObjectMetadata.contentLength(), s3ObjectMetadata.lastModified(), resource.getName()); + RequestMetaData metaData = new RequestMetaData(resource.getSize(), resource.getLastModifiedDate().getTime(), resource.getName()); // Prepare the identifier RequestIdentifier identifier = new RequestIdentifier(resource.getUri()); identifier.setParentResourceId(parentResource); // Prepare the request - IdentificationRequest request = new S3IdentificationRequest(metaData, identifier, s3Client); + IdentificationRequest request; + long maxBytesToScan = config.getProperties().getLong("profile.maxBytesToScan", -1); + if (maxBytesToScan > -1 && maxBytesToScan < DEFAULT_WINDOW_SIZE) { + request = new S3IdentificationRequest(metaData, identifier, s3Client, (int) maxBytesToScan); + } else { + request = new S3IdentificationRequest(metaData, identifier, s3Client, DEFAULT_WINDOW_SIZE); + } + if (droidCore.passesIdentificationFilter(request)) { try { @@ -86,7 +94,7 @@ public void onS3Event(AbstractProfileResource resource, ResourceId parentResourc public S3Client getS3Client(AbstractProfileResource resource) { ProxyUtils proxyUtils = new ProxyUtils(config); - S3ClientFactory s3ClientFactory = new S3ClientFactory(proxyUtils.getProxySettings(resource)); + s3ClientFactory.init(proxyUtils.getProxySettings(resource)); return s3ClientFactory.getS3Client(); } @@ -109,4 +117,8 @@ public DroidGlobalConfig getConfig() { public void setConfig(DroidGlobalConfig config) { this.config = config; } + + public void setS3ClientFactory(S3ClientFactory s3ClientFactory) { + this.s3ClientFactory = s3ClientFactory; + } } diff --git a/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3Walker.java b/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3Walker.java index 2aeb9c00b..09c27be89 100644 --- a/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3Walker.java +++ b/droid-results/src/main/java/uk/gov/nationalarchives/droid/submitter/S3Walker.java @@ -65,22 +65,25 @@ public S3Walker(final ProgressMonitor progressMonitor, final ResultHandler resul public void walk(AbstractProfileResource resource) { S3Result s3Result = getS3Result(resource); progressMonitor.setTargetCount(s3Result.totalCount()); - ArrayList keysList = new ArrayList<>(s3Result.dirToFileMap().keySet()); + ArrayList keysList = new ArrayList<>(s3Result.dirToObjectInfo().keySet()); Map pathToResourceId = new HashMap<>(); keysList.sort(Comparator.comparingInt(String::length)); for (int i=0; i < keysList.size(); i++) { URI dirUri = URI.create(keysList.get(i)); Path dirPath = getPath(dirUri); ResourceId fileParentNode = null; - if (dirPath.getParent() != null) { + if (dirPath.getParent() != null && s3Result.totalCount() > 2) { progressMonitor.startJob(dirUri); ResourceId parent = dirPath.getParent() == null ? null : pathToResourceId.get(dirPath.getParent().toUri().toString()); fileParentNode = handleS3Directory(dirPath, parent, i+1); pathToResourceId.put(dirUri + FORWARD_SLASH, fileParentNode); } - for (String objectUri: s3Result.dirToFileMap().get(keysList.get(i))) { - progressMonitor.startJob(URI.create(objectUri.replaceAll(" ", "%20"))); - s3EventHandler.onS3Event(new S3ProfileResource(objectUri), fileParentNode); + for (S3ObjectInfo objectInfo: s3Result.dirToObjectInfo().get(keysList.get(i))) { + progressMonitor.startJob(URI.create(objectInfo.key().replaceAll(" ", "%20"))); + S3ProfileResource fileProfileResource = new S3ProfileResource(objectInfo.key); + fileProfileResource.setLastModifiedDate(objectInfo.lastModified); + fileProfileResource.setSize(objectInfo.size); + s3EventHandler.onS3Event(fileProfileResource, fileParentNode); } } } @@ -106,7 +109,7 @@ private S3Result getS3Result(AbstractProfileResource resource) { Iterable contents = objectList.contents(); String bucket = objectList.bucket(); - Map> dirToFileMap = new HashMap<>(); + Map> dirToFileMap = new HashMap<>(); int totalCount = 0; String uriWithBucket = S3_SCHEME + bucket + FORWARD_SLASH; @@ -121,8 +124,8 @@ private S3Result getS3Result(AbstractProfileResource resource) { } if (!dirToFileMap.containsKey(parent)) { - List existingKeys = new ArrayList<>(); - existingKeys.add(keyUri); + List existingKeys = new ArrayList<>(); + existingKeys.add(new S3ObjectInfo(keyUri, new Date(s3Object.lastModified().toEpochMilli()), s3Object.size())); dirToFileMap.put(parent, existingKeys); if (FORWARD_SLASH.equals(URI.create(parent).getPath())) { totalCount = totalCount + 1; @@ -131,16 +134,16 @@ private S3Result getS3Result(AbstractProfileResource resource) { } } else { - List existingKeys = dirToFileMap.get(parent); - existingKeys.add(keyUri); + List existingKeys = dirToFileMap.get(parent); + existingKeys.add(new S3ObjectInfo(keyUri, new Date(s3Object.lastModified().toEpochMilli()), s3Object.size())); dirToFileMap.put(parent, existingKeys); totalCount++; } } return new S3Result(dirToFileMap, totalCount); } - - private record S3Result(Map> dirToFileMap, int totalCount) { + private record S3ObjectInfo(String key, Date lastModified, long size) {} + private record S3Result(Map> dirToObjectInfo, int totalCount) { } private Path getPath(URI uri) { diff --git a/droid-results/src/main/resources/META-INF/spring-results.xml b/droid-results/src/main/resources/META-INF/spring-results.xml index ef13ab80a..4b9908711 100644 --- a/droid-results/src/main/resources/META-INF/spring-results.xml +++ b/droid-results/src/main/resources/META-INF/spring-results.xml @@ -267,6 +267,8 @@ http://www.springframework.org/schema/context http://www.springframework.org/sch + + @@ -313,6 +315,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/sch + diff --git a/droid-results/src/test/java/uk/gov/nationalarchives/droid/submitter/S3EventHandlerTest.java b/droid-results/src/test/java/uk/gov/nationalarchives/droid/submitter/S3EventHandlerTest.java new file mode 100644 index 000000000..55a077031 --- /dev/null +++ b/droid-results/src/test/java/uk/gov/nationalarchives/droid/submitter/S3EventHandlerTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2016, The National Archives + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the The National Archives nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.gov.nationalarchives.droid.submitter; + +import org.apache.commons.configuration2.PropertiesConfiguration; +import org.apache.commons.configuration2.ex.ConfigurationException; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3Uri; +import uk.gov.nationalarchives.droid.core.interfaces.AsynchDroid; +import uk.gov.nationalarchives.droid.core.interfaces.IdentificationRequest; +import uk.gov.nationalarchives.droid.core.interfaces.ResultHandler; +import uk.gov.nationalarchives.droid.core.interfaces.config.DroidGlobalConfig; +import uk.gov.nationalarchives.droid.core.interfaces.http.S3ClientFactory; +import uk.gov.nationalarchives.droid.core.interfaces.resource.RequestMetaData; +import uk.gov.nationalarchives.droid.core.interfaces.resource.S3IdentificationRequest; +import uk.gov.nationalarchives.droid.profile.S3ProfileResource; +import uk.gov.nationalarchives.droid.profile.throttle.SubmissionThrottle; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class S3EventHandlerTest { + @Test + public void testS3EventHandlerProvidesCorrectMetadata() { + ArgumentCaptor> droidCoreCaptor = ArgumentCaptor.forClass(IdentificationRequest.class); + S3EventHandler s3EventHandler = getS3EventHandler(droidCoreCaptor, 100, true); + S3ProfileResource s3ProfileResource = new S3ProfileResource("s3://bucket/object"); + + s3EventHandler.onS3Event(s3ProfileResource, null); + List> allValues = droidCoreCaptor.getAllValues(); + + RequestMetaData requestMetaData = allValues.getFirst().getRequestMetaData(); + assertEquals(Long.valueOf(1), requestMetaData.getSize()); + assertEquals(Long.valueOf(0), requestMetaData.getTime()); + assertEquals("object", requestMetaData.getName()); + } + + @Test + public void testS3EventHandlerUsesCorrectWindowSizeForSmallMaxBytes() { + ArgumentCaptor> droidCoreCaptor = ArgumentCaptor.forClass(IdentificationRequest.class); + S3EventHandler s3EventHandler = getS3EventHandler(droidCoreCaptor, 100, true); + S3ProfileResource s3ProfileResource = new S3ProfileResource("s3://bucket/object"); + + s3EventHandler.onS3Event(s3ProfileResource, null); + List> allValues = droidCoreCaptor.getAllValues(); + + assertEquals(100, ((S3IdentificationRequest) allValues.getFirst()).getWindowSize()); + } + + @Test + public void testS3EventHandlerUsesCorrectWindowSizeForLargeMaxBytes() { + ArgumentCaptor> droidCoreCaptor = ArgumentCaptor.forClass(IdentificationRequest.class); + S3EventHandler s3EventHandler = getS3EventHandler(droidCoreCaptor, 100 * 1024 * 1024, true); + S3ProfileResource s3ProfileResource = new S3ProfileResource("s3://bucket/object"); + + s3EventHandler.onS3Event(s3ProfileResource, null); + List> allValues = droidCoreCaptor.getAllValues(); + + assertEquals(4 * 1024 * 1024, ((S3IdentificationRequest) allValues.getFirst()).getWindowSize()); + } + + @Test + public void testS3EventHandlerUsesCorrectWindowSizeForInfiniteBytes() { + ArgumentCaptor> droidCoreCaptor = ArgumentCaptor.forClass(IdentificationRequest.class); + S3EventHandler s3EventHandler = getS3EventHandler(droidCoreCaptor, 100 * 1024 * 1024, true); + S3ProfileResource s3ProfileResource = new S3ProfileResource("s3://bucket/object"); + + s3EventHandler.onS3Event(s3ProfileResource, null); + List> allValues = droidCoreCaptor.getAllValues(); + + assertEquals(4 * 1024 * 1024, ((S3IdentificationRequest) allValues.getFirst()).getWindowSize()); + } + + @Test + public void testS3EventHandlerDoesNotSubmitIfItDoesNotPassFilter() { + ArgumentCaptor> droidCoreCaptor = ArgumentCaptor.forClass(IdentificationRequest.class); + S3EventHandler s3EventHandler = getS3EventHandler(droidCoreCaptor, 100 * 1024 * 1024, false); + S3ProfileResource s3ProfileResource = new S3ProfileResource("s3://bucket/object"); + + s3EventHandler.onS3Event(s3ProfileResource, null); + List> allValues = droidCoreCaptor.getAllValues(); + + assertEquals(0, allValues.size()); + } + + private static S3EventHandler getS3EventHandler(ArgumentCaptor> droidCoreCaptor, int maxBytesToScan, boolean passesIdentification) { + AsynchDroid droidCore = mock(AsynchDroid.class); + SubmissionThrottle submissionThrottle = mock(SubmissionThrottle.class); + ResultHandler resultHandler = mock(ResultHandler.class); + DroidGlobalConfig droidGlobalConfig = mock(DroidGlobalConfig.class); + PropertiesConfiguration propertiesConfiguration = new PropertiesConfiguration(); + + when(droidCore.passesIdentificationFilter(any())).thenReturn(passesIdentification); + + when(droidCore.submit(droidCoreCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null)); + try { + propertiesConfiguration.read(new StringReader("profile.maxBytesToScan=" + maxBytesToScan + "\nupdate.proxy=false")); + } catch (ConfigurationException | IOException e) { + throw new RuntimeException(e); + } + when(droidGlobalConfig.getProperties()).thenReturn(propertiesConfiguration); + + S3ClientFactory s3ClientFactory = mock(S3ClientFactory.class); + S3Client s3Client = mock(S3Client.class); + when(s3ClientFactory.getS3Client()).thenReturn(s3Client); + return new S3EventHandler(droidCore, submissionThrottle, resultHandler, droidGlobalConfig, s3ClientFactory); + } +} diff --git a/droid-swing-ui/pom.xml b/droid-swing-ui/pom.xml index e1d4aff2a..8d06e2d43 100644 --- a/droid-swing-ui/pom.xml +++ b/droid-swing-ui/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/droid-tools/pom.xml b/droid-tools/pom.xml index 798453de6..2617d57fe 100644 --- a/droid-tools/pom.xml +++ b/droid-tools/pom.xml @@ -5,7 +5,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT ../droid-parent diff --git a/pom.xml b/pom.xml index 76626b216..64987ff79 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ droid-parent uk.gov.nationalarchives - 6.9.7-SNAPSHOT + 6.9.8-SNAPSHOT droid-parent