Skip to content

Commit 654e0cd

Browse files
committed
chore: drop commons-lang3 dependency and replace its usages with core Java
Fixes #2735
1 parent db1e4a4 commit 654e0cd

File tree

11 files changed

+60
-66
lines changed

11 files changed

+60
-66
lines changed

pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@
9292
<commons-codec.version>1.19.0</commons-codec.version>
9393
<commons-compress.version>1.28.0</commons-compress.version>
9494
<commons-io.version>2.20.0</commons-io.version>
95-
<commons-lang3.version>3.19.0</commons-lang3.version>
9695
<httpclient.version>4.5.14</httpclient.version>
9796
<httpmime.version>4.5.14</httpmime.version>
9897
<httpcore.version>4.4.16</httpcore.version>
@@ -248,11 +247,6 @@
248247
<artifactId>commons-compress</artifactId>
249248
<version>${commons-compress.version}</version>
250249
</dependency>
251-
<dependency>
252-
<groupId>org.apache.commons</groupId>
253-
<artifactId>commons-lang3</artifactId>
254-
<version>${commons-lang3.version}</version>
255-
</dependency>
256250
<dependency>
257251
<groupId>commons-codec</groupId>
258252
<artifactId>commons-codec</artifactId>

server/src/main/java/com/adobe/testing/s3mock/KmsValidationFilter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_SERVER_SIDE_ENCRYPTION;
2020
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID;
21-
import static org.apache.commons.lang3.StringUtils.isBlank;
2221
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
2322
import static org.springframework.http.HttpStatus.BAD_REQUEST;
2423
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
@@ -70,7 +69,7 @@ protected void doFilterInternal(HttpServletRequest request,
7069
var encryptionKeyId = request.getHeader(X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID);
7170

7271
if (AWS_KMS.equals(encryptionTypeHeader)
73-
&& !isBlank(encryptionKeyId)
72+
&& encryptionKeyId != null && !encryptionKeyId.isBlank()
7473
&& !keystore.validateKeyId(encryptionKeyId)) {
7574
LOG.info("Received invalid KMS key ID {}. Sending error response.", encryptionKeyId);
7675

@@ -90,7 +89,7 @@ protected void doFilterInternal(HttpServletRequest request,
9089

9190
response.flushBuffer();
9291
} else if (AWS_KMS.equals(encryptionTypeHeader)
93-
&& !isBlank(encryptionKeyId)
92+
&& encryptionKeyId != null && !encryptionKeyId.isBlank()
9493
&& keystore.validateKeyId(encryptionKeyId)) {
9594
LOG.info("Received valid KMS key ID {}.", encryptionKeyId);
9695
filterChain.doFilter(request, response);

server/src/main/java/com/adobe/testing/s3mock/TaggingHeaderConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ private List<Tag> convertTagXml(String source) {
7676
@Nullable
7777
private static List<Tag> convertTagPairs(String source) {
7878
var tags = new ArrayList<Tag>();
79-
String[] tagPairs = StringUtils.split(source, '&');
79+
// TODO: does it differ from StringUtils.split(source, '&'); ?
80+
String[] tagPairs = source.split("&");
8081
for (String tag : tagPairs) {
8182
tags.add(new Tag(tag));
8283
}

server/src/main/java/com/adobe/testing/s3mock/service/BucketService.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import static com.adobe.testing.s3mock.service.ServiceBase.mapContents;
3232
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_LOCATION_NAME;
3333
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_LOCATION_TYPE;
34-
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
3534
import static software.amazon.awssdk.utils.http.SdkHttpUtils.urlEncodeIgnoreSlashes;
3635

3736
import com.adobe.testing.s3mock.dto.Bucket;
@@ -592,7 +591,7 @@ public void verifyMaxKeys(Integer maxKeys) {
592591
}
593592

594593
public void verifyEncodingType(String encodingType) {
595-
if (isNotEmpty(encodingType) && !"url".equals(encodingType)) {
594+
if (encodingType != null && !encodingType.isEmpty() && !"url".equals(encodingType)) {
596595
throw INVALID_REQUEST_ENCODING_TYPE;
597596
}
598597
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.adobe.testing.s3mock.service;
2+
3+
import org.jspecify.annotations.Nullable;
4+
5+
import java.nio.file.Path;
6+
7+
public record FileChecksum(Path path, @Nullable String checksum) {
8+
}

server/src/main/java/com/adobe/testing/s3mock/service/ServiceBase.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import static com.adobe.testing.s3mock.util.HeaderUtil.checksumAlgorithmFromSdk;
2222
import static com.adobe.testing.s3mock.util.HeaderUtil.isChunkedEncoding;
2323
import static com.adobe.testing.s3mock.util.HeaderUtil.isV4Signed;
24-
import static org.apache.commons.lang3.StringUtils.isEmpty;
25-
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
2624

2725
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
2826
import com.adobe.testing.s3mock.util.AbstractAwsInputStream;
@@ -37,7 +35,6 @@
3735
import java.util.List;
3836
import java.util.function.Function;
3937
import java.util.function.UnaryOperator;
40-
import org.apache.commons.lang3.tuple.Pair;
4138
import org.jspecify.annotations.Nullable;
4239
import org.slf4j.Logger;
4340
import org.slf4j.LoggerFactory;
@@ -52,7 +49,7 @@ public void verifyChecksum(Path path, String checksum, ChecksumAlgorithm checksu
5249
DigestUtil.verifyChecksum(checksum, checksumFor, checksumAlgorithm);
5350
}
5451

55-
public Pair<Path, @Nullable String> toTempFile(InputStream inputStream, HttpHeaders httpHeaders) {
52+
public FileChecksum toTempFile(InputStream inputStream, HttpHeaders httpHeaders) {
5653
try {
5754
var tempFile = Files.createTempFile("ObjectService", "toTempFile");
5855
try (var os = Files.newOutputStream(tempFile);
@@ -61,22 +58,22 @@ public void verifyChecksum(Path path, String checksum, ChecksumAlgorithm checksu
6158
ChecksumAlgorithm algorithmFromSdk = checksumAlgorithmFromSdk(httpHeaders);
6259
if (algorithmFromSdk != null
6360
&& wrappedStream instanceof AbstractAwsInputStream awsInputStream) {
64-
return Pair.of(tempFile, awsInputStream.getChecksum());
61+
return new FileChecksum(tempFile, awsInputStream.getChecksum());
6562
}
66-
return Pair.of(tempFile, null);
63+
return new FileChecksum(tempFile, null);
6764
}
6865
} catch (IOException e) {
6966
LOG.error("Error reading from InputStream", e);
7067
throw BAD_REQUEST_CONTENT;
7168
}
7269
}
7370

74-
public Pair<Path, @Nullable String> toTempFile(InputStream inputStream) {
71+
public FileChecksum toTempFile(InputStream inputStream) {
7572
try {
7673
var tempFile = Files.createTempFile("ObjectService", "toTempFile");
7774
try (var os = Files.newOutputStream(tempFile)) {
7875
inputStream.transferTo(os);
79-
return Pair.of(tempFile, null);
76+
return new FileChecksum(tempFile, null);
8077
}
8178
} catch (IOException e) {
8279
LOG.error("Error reading from InputStream", e);
@@ -99,7 +96,7 @@ static <T> List<T> filterBy(
9996
Function<T, String> function,
10097
@Nullable String compareTo
10198
) {
102-
if (isNotEmpty(compareTo)) {
99+
if (compareTo != null && !compareTo.isEmpty()) {
103100
return contents
104101
.stream()
105102
.filter(content -> function.apply(content).compareTo(compareTo) > 0)
@@ -161,7 +158,7 @@ static <T> List<String> collapseCommonPrefixes(
161158
Function<T, String> function
162159
) {
163160
var commonPrefixes = new ArrayList<String>();
164-
if (isEmpty(delimiter)) {
161+
if (delimiter == null || delimiter.isEmpty()) {
165162
return commonPrefixes;
166163
}
167164

server/src/main/java/com/adobe/testing/s3mock/store/MultipartStore.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import static java.nio.file.Files.newDirectoryStream;
2323
import static java.nio.file.Files.newOutputStream;
2424
import static org.apache.commons.io.FileUtils.openInputStream;
25-
import static org.apache.commons.lang3.StringUtils.isBlank;
2625

2726
import com.adobe.testing.s3mock.S3Exception;
2827
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
@@ -55,7 +54,6 @@
5554
import java.util.stream.StreamSupport;
5655
import org.apache.commons.io.FileUtils;
5756
import org.apache.commons.io.input.BoundedInputStream;
58-
import org.apache.commons.lang3.stream.Streams;
5957
import org.jspecify.annotations.Nullable;
6058
import org.slf4j.Logger;
6159
import org.slf4j.LoggerFactory;
@@ -135,8 +133,7 @@ public List<MultipartUpload> listMultipartUploads(BucketMetadata bucketMetadata,
135133
return Collections.emptyList();
136134
}
137135
try (var paths = Files.newDirectoryStream(multipartsFolder)) {
138-
return Streams
139-
.of(paths)
136+
return StreamSupport.stream(paths.spliterator(), false)
140137
.map(
141138
path -> {
142139
var fileName = path.getFileName().toString();
@@ -146,7 +143,7 @@ public List<MultipartUpload> listMultipartUploads(BucketMetadata bucketMetadata,
146143
.filter(Objects::nonNull)
147144
.filter(uploadMetadata -> !uploadMetadata.completed())
148145
.map(MultipartUploadInfo::upload)
149-
.filter(upload -> isBlank(prefix) || upload.key().startsWith(prefix))
146+
.filter(upload -> prefix == null || prefix.isBlank() || upload.key().startsWith(prefix))
150147
.toList();
151148
} catch (IOException e) {
152149
throw new IllegalStateException("Could not load buckets from data directory ", e);

server/src/main/java/com/adobe/testing/s3mock/util/DigestUtil.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.apache.commons.codec.binary.Base64;
3838
import org.apache.commons.codec.binary.Hex;
3939
import org.apache.commons.codec.digest.DigestUtils;
40-
import org.apache.commons.lang3.ArrayUtils;
4140
import org.jspecify.annotations.Nullable;
4241
import software.amazon.awssdk.checksums.SdkChecksum;
4342
import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
@@ -122,17 +121,14 @@ private static byte[] checksum(InputStream is, ChecksumAlgorithm algorithm) {
122121

123122
private static byte[] checksum(List<Path> paths, ChecksumAlgorithm algorithm) {
124123
SdkChecksum sdkChecksum = SdkChecksum.forAlgorithm(algorithm);
125-
var allChecksums = new byte[0];
126124
for (var path : paths) {
127125
try (var inputStream = Files.newInputStream(path)) {
128-
allChecksums = ArrayUtils.addAll(allChecksums, checksum(inputStream, algorithm));
126+
sdkChecksum.update(checksum(inputStream, algorithm));
129127
} catch (IOException e) {
130128
throw new IllegalStateException("Could not read from path " + path, e);
131129
}
132130
}
133-
sdkChecksum.update(allChecksums, 0, allChecksums.length);
134-
allChecksums = sdkChecksum.getChecksumBytes();
135-
return allChecksums;
131+
return sdkChecksum.getChecksumBytes();
136132
}
137133

138134
/**
@@ -157,7 +153,7 @@ private static byte[] checksum(List<Path> paths, ChecksumAlgorithm algorithm) {
157153
* @return A special hex digest that is used for files uploaded in parts.
158154
*/
159155
public static String hexDigestMultipart(List<Path> paths) {
160-
return DigestUtils.md5Hex(md5(null, paths)) + "-" + paths.size();
156+
return BinaryUtils.toBase64(md5(null, paths)) + "-" + paths.size();
161157
}
162158

163159
/**
@@ -271,15 +267,15 @@ private static byte[] md5(@Nullable String salt, InputStream inputStream) {
271267
}
272268

273269
private static byte[] md5(@Nullable String salt, List<Path> paths) {
274-
var allMd5s = new byte[0];
270+
MessageDigest md5 = getMd5Digest();
275271
for (var path : paths) {
276272
try (var inputStream = Files.newInputStream(path)) {
277-
allMd5s = ArrayUtils.addAll(allMd5s, md5(salt, inputStream));
273+
md5.update(md5(salt, inputStream));
278274
} catch (IOException e) {
279275
throw new IllegalStateException("Could not read from path " + path, e);
280276
}
281277
}
282-
return allMd5s;
278+
return md5.digest();
283279
}
284280

285281
private static MessageDigest messageDigest(@Nullable String salt) {

server/src/main/java/com/adobe/testing/s3mock/util/HeaderUtil.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_SDK_CHECKSUM_ALGORITHM;
2828
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_SERVER_SIDE_ENCRYPTION;
2929
import static com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_STORAGE_CLASS;
30-
import static org.apache.commons.lang3.StringUtils.isNotBlank;
31-
import static org.apache.commons.lang3.Strings.CI;
3230

3331
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
3432
import com.adobe.testing.s3mock.dto.StorageClass;
@@ -73,7 +71,7 @@ public static Map<String, String> userMetadataHeadersFrom(S3ObjectMetadata s3Obj
7371
if (s3ObjectMetadata.userMetadata() != null) {
7472
s3ObjectMetadata.userMetadata()
7573
.forEach((key, value) -> {
76-
if (CI.startsWith(key, HEADER_X_AMZ_META_PREFIX)) {
74+
if (key.regionMatches(true, 0, HEADER_X_AMZ_META_PREFIX, 0, HEADER_X_AMZ_META_PREFIX.length())) {
7775
metadataHeaders.put(key, value);
7876
} else {
7977
// support case where metadata was stored locally in legacy format
@@ -106,7 +104,7 @@ public static Map<String, String> storageClassHeadersFrom(S3ObjectMetadata s3Obj
106104
*/
107105
public static Map<String, String> userMetadataFrom(HttpHeaders headers) {
108106
return parseHeadersToMap(headers,
109-
header -> CI.startsWith(header, HEADER_X_AMZ_META_PREFIX));
107+
header -> header.regionMatches(true, 0, HEADER_X_AMZ_META_PREFIX, 0, HEADER_X_AMZ_META_PREFIX.length()));
110108
}
111109

112110
/**
@@ -117,12 +115,12 @@ public static Map<String, String> userMetadataFrom(HttpHeaders headers) {
117115
*/
118116
public static Map<String, String> storeHeadersFrom(HttpHeaders headers) {
119117
return parseHeadersToMap(headers,
120-
header -> (CI.equals(header, HttpHeaders.EXPIRES)
121-
|| CI.equals(header, HttpHeaders.CONTENT_LANGUAGE)
122-
|| CI.equals(header, HttpHeaders.CONTENT_DISPOSITION)
123-
|| (CI.equals(header, HttpHeaders.CONTENT_ENCODING)
118+
header -> (HttpHeaders.EXPIRES.equalsIgnoreCase(header)
119+
|| HttpHeaders.CONTENT_LANGUAGE.equalsIgnoreCase(header)
120+
|| HttpHeaders.CONTENT_DISPOSITION.equalsIgnoreCase(header)
121+
|| (HttpHeaders.CONTENT_ENCODING.equalsIgnoreCase(header)
124122
&& !isOnlyChunkedEncoding(headers))
125-
|| CI.equals(header, HttpHeaders.CACHE_CONTROL)
123+
|| HttpHeaders.CACHE_CONTROL.equalsIgnoreCase(header)
126124
));
127125
}
128126

@@ -134,7 +132,7 @@ public static Map<String, String> storeHeadersFrom(HttpHeaders headers) {
134132
*/
135133
public static Map<String, String> encryptionHeadersFrom(HttpHeaders headers) {
136134
return parseHeadersToMap(headers,
137-
header -> CI.startsWith(header, X_AMZ_SERVER_SIDE_ENCRYPTION));
135+
header -> header.regionMatches(true, 0, X_AMZ_SERVER_SIDE_ENCRYPTION, 0, X_AMZ_SERVER_SIDE_ENCRYPTION.length()));
138136
}
139137

140138
private static Map<String, String> parseHeadersToMap(HttpHeaders headers,
@@ -146,9 +144,13 @@ private static Map<String, String> parseHeadersToMap(HttpHeaders headers,
146144
entry -> {
147145
if (matcher.test(entry.getKey())
148146
&& entry.getValue() != null
149-
&& !entry.getValue().isEmpty()
150-
&& isNotBlank(entry.getValue().get(0))) {
151-
return new SimpleEntry<>(entry.getKey(), entry.getValue().get(0));
147+
&& !entry.getValue().isEmpty()) {
148+
String value = entry.getValue().get(0);
149+
if (value != null && !value.isBlank()) {
150+
return new SimpleEntry<>(entry.getKey(), entry.getValue().get(0));
151+
} else {
152+
return null;
153+
}
152154
} else {
153155
return null;
154156
}
@@ -201,8 +203,9 @@ public static Map<String, String> overrideHeadersFrom(Map<String, String> queryP
201203
.stream()
202204
.map(
203205
entry -> {
204-
if (isNotBlank(mapHeaderName(entry.getKey()))) {
205-
return new SimpleEntry<>(mapHeaderName(entry.getKey()), entry.getValue());
206+
String mapHeaderName = mapHeaderName(entry.getKey());
207+
if (!mapHeaderName.isBlank()) {
208+
return new SimpleEntry<>(mapHeaderName, entry.getValue());
206209
} else {
207210
return null;
208211
}

server/src/test/kotlin/com/adobe/testing/s3mock/MultipartControllerTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ import com.adobe.testing.s3mock.dto.StorageClass
3131
import com.adobe.testing.s3mock.dto.Tag
3232
import com.adobe.testing.s3mock.dto.VersioningConfiguration
3333
import com.adobe.testing.s3mock.service.BucketService
34+
import com.adobe.testing.s3mock.service.FileChecksum
3435
import com.adobe.testing.s3mock.service.MultipartService
3536
import com.adobe.testing.s3mock.service.ObjectService
3637
import com.adobe.testing.s3mock.store.KmsKeyStore
3738
import com.adobe.testing.s3mock.store.MultipartUploadInfo
38-
import org.apache.commons.lang3.tuple.Pair
3939
import org.junit.jupiter.api.Test
4040
import org.mockito.ArgumentMatchers.anyList
4141
import org.mockito.ArgumentMatchers.anyString
@@ -1051,7 +1051,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
10511051
val uploadId = UUID.randomUUID()
10521052

10531053
val temp = java.nio.file.Files.createTempFile("junie", "part")
1054-
whenever(multipartService.toTempFile(any(), any())).thenReturn(Pair.of(temp, null))
1054+
whenever(multipartService.toTempFile(any(), any())).thenReturn(FileChecksum(temp, null))
10551055
whenever(
10561056
multipartService.putPart(eq(TEST_BUCKET_NAME), eq("my/key.txt"), eq(uploadId), eq(1), eq(temp), any())
10571057
).thenReturn("etag-123")
@@ -1359,7 +1359,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
13591359
val uploadId = UUID.randomUUID()
13601360

13611361
val temp = java.nio.file.Files.createTempFile("junie", "part")
1362-
whenever(multipartService.toTempFile(any(), any())).thenReturn(Pair.of(temp, null))
1362+
whenever(multipartService.toTempFile(any(), any())).thenReturn(FileChecksum(temp, null))
13631363

13641364
// when checksum headers are present, controller should call verifyChecksum and return header
13651365
val checksum = "abc123checksum"
@@ -1394,7 +1394,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
13941394
fun testUploadPart_InvalidPartNumber_BadRequest() {
13951395
// Arrange: toTempFile is called before validations
13961396
val temp = java.nio.file.Files.createTempFile("junie", "part")
1397-
whenever(multipartService.toTempFile(any(), any())).thenReturn(Pair.of(temp, null))
1397+
whenever(multipartService.toTempFile(any(), any())).thenReturn(FileChecksum(temp, null))
13981398

13991399
val bucketMeta = bucketMetadata()
14001400
whenever(bucketService.verifyBucketExists(TEST_BUCKET_NAME)).thenReturn(bucketMeta)
@@ -1425,7 +1425,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
14251425
fun testUploadPart_NoSuchBucket() {
14261426
// toTempFile happens first
14271427
val temp = java.nio.file.Files.createTempFile("junie", "part")
1428-
whenever(multipartService.toTempFile(any(), any())).thenReturn(Pair.of(temp, null))
1428+
whenever(multipartService.toTempFile(any(), any())).thenReturn(FileChecksum(temp, null))
14291429

14301430
// bucket missing
14311431
doThrow(S3Exception.NO_SUCH_BUCKET)
@@ -1452,7 +1452,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
14521452
@Test
14531453
fun testUploadPart_NoSuchUpload() {
14541454
val temp = java.nio.file.Files.createTempFile("junie", "part")
1455-
whenever(multipartService.toTempFile(any(), any())).thenReturn(Pair.of(temp, null))
1455+
whenever(multipartService.toTempFile(any(), any())).thenReturn(FileChecksum(temp, null))
14561456

14571457
val bucketMeta = bucketMetadata()
14581458
whenever(bucketService.verifyBucketExists(TEST_BUCKET_NAME)).thenReturn(bucketMeta)

0 commit comments

Comments
 (0)