Skip to content

Commit b3bb13f

Browse files
committed
Simplification, readability, immutability
Also: use Kotlin Regex, the API is simpler than Java's Pattern API.
1 parent 08a1513 commit b3bb13f

File tree

7 files changed

+90
-213
lines changed

7 files changed

+90
-213
lines changed

server/src/main/kotlin/com/adobe/testing/s3mock/service/BucketService.kt

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import com.adobe.testing.s3mock.store.BucketStore
4040
import com.adobe.testing.s3mock.store.ObjectStore
4141
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_LOCATION_NAME
4242
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_LOCATION_TYPE
43-
import software.amazon.awssdk.utils.http.SdkHttpUtils
43+
import software.amazon.awssdk.utils.http.SdkHttpUtils.urlEncodeIgnoreSlashes
4444
import java.util.UUID
4545
import java.util.concurrent.ConcurrentHashMap
4646

@@ -74,20 +74,18 @@ open class BucketService(
7474

7575
var buckets = bucketStore
7676
.listBuckets()
77-
.asSequence()
7877
.filter { it.name.startsWith(normalizedPrefix) }
7978
.sortedBy { it.name }
8079
.map { Bucket.from(it) }
81-
.toList()
8280

83-
bucketRegion?.let { region ->
84-
buckets = buckets.filter { it.bucketRegion == region.toString() }
81+
bucketRegion?.let {
82+
buckets = buckets.filter { it.bucketRegion == it.toString() }
8583
}
8684

8785
var nextContinuationToken: String? = null
8886

89-
continuationToken?.let { token ->
90-
val continueAfter = listBucketsPagingStateCache.remove(token)
87+
continuationToken?.let {
88+
val continueAfter = listBucketsPagingStateCache.remove(it)
9189
buckets = filterBy(buckets, Bucket::name, continueAfter)
9290
}
9391

@@ -183,11 +181,9 @@ open class BucketService(
183181
fun getS3Objects(bucketName: String, prefix: String?): List<S3Object> {
184182
val bucketMetadata = bucketStore.getBucketMetadata(bucketName)
185183
return bucketStore.lookupIdsInBucket(prefix, bucketName)
186-
.asSequence()
187184
.mapNotNull { id -> objectStore.getS3ObjectMetadata(bucketMetadata, id, null) }
188185
.map(S3Object::from)
189186
.sortedBy(S3Object::key)
190-
.toList()
191187
}
192188

193189
fun listVersions(
@@ -260,8 +256,8 @@ open class BucketService(
260256
): ListBucketResultV2 {
261257
if (maxKeys == 0) {
262258
return ListBucketResultV2(
263-
mutableListOf(),
264-
mutableListOf(),
259+
listOf(),
260+
listOf(),
265261
continuationToken,
266262
delimiter,
267263
encodingType,
@@ -319,27 +315,27 @@ open class BucketService(
319315
var returnCommonPrefixes = commonPrefixes
320316

321317
if (encodingType == "url") {
322-
contents = mapContents(contents) {
318+
contents = contents.map {
323319
S3Object(
324320
it.checksumAlgorithm,
325321
it.checksumType,
326322
it.etag,
327-
SdkHttpUtils.urlEncodeIgnoreSlashes(it.key),
323+
urlEncodeIgnoreSlashes(it.key),
328324
it.lastModified,
329325
it.owner,
330326
it.restoreStatus,
331327
it.size,
332328
it.storageClass
333329
)
334330
}
335-
returnPrefix = SdkHttpUtils.urlEncodeIgnoreSlashes(prefix)
336-
returnStartAfter = SdkHttpUtils.urlEncodeIgnoreSlashes(startAfter)
337-
returnCommonPrefixes = mapContents(commonPrefixes) { v: String? -> SdkHttpUtils.urlEncodeIgnoreSlashes(v) }
338-
returnDelimiter = SdkHttpUtils.urlEncodeIgnoreSlashes(delimiter)
331+
returnPrefix = urlEncodeIgnoreSlashes(prefix)
332+
returnStartAfter = urlEncodeIgnoreSlashes(startAfter)
333+
returnCommonPrefixes = commonPrefixes.map { urlEncodeIgnoreSlashes(it) }
334+
returnDelimiter = urlEncodeIgnoreSlashes(delimiter)
339335
}
340336

341337
return ListBucketResultV2(
342-
returnCommonPrefixes.map { Prefix(it) }.toMutableList(),
338+
returnCommonPrefixes.map { Prefix(it) },
343339
contents,
344340
continuationToken,
345341
returnDelimiter,
@@ -365,8 +361,16 @@ open class BucketService(
365361
): ListBucketResult {
366362
if (maxKeys == 0) {
367363
return ListBucketResult(
368-
emptyList(), mutableListOf(), null, encodingType,
369-
false, marker, maxKeys, bucketName, marker, prefix
364+
emptyList(),
365+
listOf(),
366+
null,
367+
encodingType,
368+
false,
369+
marker,
370+
maxKeys,
371+
bucketName,
372+
marker,
373+
prefix
370374
)
371375
}
372376

@@ -391,21 +395,21 @@ open class BucketService(
391395
var returnCommonPrefixes = commonPrefixes
392396

393397
if (encodingType == "url") {
394-
contents = mapContents(contents) {
398+
contents = contents.map {
395399
S3Object(
396400
it.checksumAlgorithm,
397401
it.checksumType,
398402
it.etag,
399-
SdkHttpUtils.urlEncodeIgnoreSlashes(it.key),
403+
urlEncodeIgnoreSlashes(it.key),
400404
it.lastModified,
401405
it.owner,
402406
it.restoreStatus,
403407
it.size,
404408
it.storageClass
405409
)
406410
}
407-
returnPrefix = SdkHttpUtils.urlEncodeIgnoreSlashes(prefix)
408-
returnCommonPrefixes = mapContents(commonPrefixes) { v: String? -> SdkHttpUtils.urlEncodeIgnoreSlashes(v) }
411+
returnPrefix = urlEncodeIgnoreSlashes(prefix)
412+
returnCommonPrefixes = commonPrefixes.map { urlEncodeIgnoreSlashes(it) }
409413
}
410414

411415
return ListBucketResult(
@@ -422,20 +426,20 @@ open class BucketService(
422426
)
423427
}
424428

425-
fun bucketLocationHeaders(bucketMetadata: BucketMetadata): MutableMap<String, String> {
429+
fun bucketLocationHeaders(bucketMetadata: BucketMetadata): Map<String, String> {
426430
val info = bucketMetadata.bucketInfo
427431
val loc = bucketMetadata.locationInfo
428432
return if (
429433
info?.type == BucketType.DIRECTORY &&
430434
loc?.name != null &&
431435
loc.type != null
432436
) {
433-
mutableMapOf(
437+
mapOf(
434438
X_AMZ_BUCKET_LOCATION_NAME to loc.name,
435439
X_AMZ_BUCKET_LOCATION_TYPE to loc.type.toString()
436440
)
437441
} else {
438-
mutableMapOf()
442+
mapOf()
439443
}
440444
}
441445

server/src/main/kotlin/com/adobe/testing/s3mock/service/MultipartService.kt

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ import com.adobe.testing.s3mock.store.MultipartUploadInfo
3636
import org.slf4j.Logger
3737
import org.slf4j.LoggerFactory
3838
import org.springframework.http.HttpRange
39-
import software.amazon.awssdk.utils.http.SdkHttpUtils
39+
import software.amazon.awssdk.utils.http.SdkHttpUtils.urlEncodeIgnoreSlashes
4040
import java.nio.file.Path
4141
import java.util.Date
4242
import java.util.UUID
43-
import java.util.stream.Collectors
4443

4544
open class MultipartService(private val bucketStore: BucketStore, private val multipartStore: MultipartStore) :
4645
ServiceBase() {
@@ -105,16 +104,14 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu
105104
val id = bucketMetadata.getID(key) ?: return null
106105
val multipartUpload = multipartStore.getMultipartUpload(bucketMetadata, uploadId, false)
107106
var parts = multipartStore.getMultipartUploadParts(bucketMetadata, id, uploadId)
108-
.stream()
109-
.toList()
110107

111-
parts = filterBy<Part>(parts, Part::partNumber, partNumberMarker)
108+
parts = filterBy(parts, Part::partNumber, partNumberMarker)
112109

113110
var nextPartNumberMarker: Int? = null
114111
var isTruncated = false
115112
if (parts.size > maxParts) {
116113
parts = parts.subList(0, maxParts)
117-
nextPartNumberMarker = parts[maxParts - 1]!!.partNumber
114+
nextPartNumberMarker = parts[maxParts - 1].partNumber
118115
isTruncated = true
119116
}
120117

@@ -223,59 +220,56 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu
223220
val bucketMetadata = bucketStore.getBucketMetadata(bucketName)
224221
var contents = multipartStore
225222
.listMultipartUploads(bucketMetadata, prefix)
226-
.stream()
227-
.filter { mu: MultipartUpload? -> mu!!.key.startsWith(normalizedPrefix) }
228-
.sorted(Comparator.comparing(MultipartUpload::key))
223+
.filter { it.key.startsWith(normalizedPrefix) }
224+
.sortedBy(MultipartUpload::key)
229225
.toList()
230226

231-
contents = filterBy<MultipartUpload>(contents, MultipartUpload::key, keyMarker)
227+
contents = filterBy(contents, MultipartUpload::key, keyMarker)
232228

233-
val commonPrefixes = collapseCommonPrefixes<MultipartUpload>(
229+
val commonPrefixes = collapseCommonPrefixes(
234230
prefix,
235231
delimiter,
236232
contents,
237233
MultipartUpload::key
238234
)
239-
contents = filterBy<MultipartUpload>(contents, MultipartUpload::key, commonPrefixes)
235+
contents = filterBy(contents, MultipartUpload::key, commonPrefixes)
240236
if (maxUploads < contents.size) {
241237
contents = contents.subList(0, maxUploads)
242238
isTruncated = true
243239
if (maxUploads > 0) {
244-
nextKeyMarker = contents[maxUploads - 1]!!.key
245-
nextUploadIdMarker = contents[maxUploads - 1]!!.uploadId
240+
nextKeyMarker = contents[maxUploads - 1].key
241+
nextUploadIdMarker = contents[maxUploads - 1].uploadId
246242
}
247243
}
248244

249-
var returnDelimiter: String? = delimiter
250-
var returnKeyMarker: String? = keyMarker
245+
var returnDelimiter = delimiter
246+
var returnKeyMarker = keyMarker
251247
var returnPrefix = prefix
252248
var returnCommonPrefixes = commonPrefixes
253249

254250
if ("url" == encodingType) {
255-
contents = mapContents<MultipartUpload>(
256-
contents
257-
) {
251+
contents = contents.map {
258252
MultipartUpload(
259253
it.checksumAlgorithm,
260254
it.checksumType,
261255
it.initiated,
262256
it.initiator,
263-
SdkHttpUtils.urlEncodeIgnoreSlashes(it.key),
257+
urlEncodeIgnoreSlashes(it.key),
264258
it.owner,
265259
it.storageClass,
266260
it.uploadId
267261
)
268262
}
269-
returnPrefix = SdkHttpUtils.urlEncodeIgnoreSlashes(prefix)
270-
returnCommonPrefixes = mapContents(commonPrefixes) { value: String? -> SdkHttpUtils.urlEncodeIgnoreSlashes(value) }
271-
returnDelimiter = SdkHttpUtils.urlEncodeIgnoreSlashes(delimiter)
272-
returnKeyMarker = SdkHttpUtils.urlEncodeIgnoreSlashes(keyMarker)
273-
nextKeyMarker = SdkHttpUtils.urlEncodeIgnoreSlashes(nextKeyMarker)
263+
returnPrefix = urlEncodeIgnoreSlashes(prefix)
264+
returnCommonPrefixes = commonPrefixes.map { urlEncodeIgnoreSlashes(it) }
265+
returnDelimiter = urlEncodeIgnoreSlashes(delimiter)
266+
returnKeyMarker = urlEncodeIgnoreSlashes(keyMarker)
267+
nextKeyMarker = urlEncodeIgnoreSlashes(nextKeyMarker)
274268
}
275269

276270
return ListMultipartUploadsResult(
277271
bucketName,
278-
returnCommonPrefixes.stream().map { prefix: String? -> Prefix(prefix) }.toList(),
272+
returnCommonPrefixes.map { Prefix(it) },
279273
returnDelimiter,
280274
encodingType,
281275
isTruncated,
@@ -310,10 +304,7 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu
310304
verifyMultipartParts(bucketName, id, uploadId)
311305

312306
val uploadedParts: List<Part> = multipartStore.getMultipartUploadParts(bucketMetadata, id, uploadId)
313-
val uploadedPartsMap =
314-
uploadedParts
315-
.stream()
316-
.collect(Collectors.toMap(Part::partNumber, Part::etag))
307+
val uploadedPartsMap: Map<Int, String?> = uploadedParts.associate { it.partNumber to it.etag }
317308

318309
var prevPartNumber = 0
319310
for (part in requestedParts) {
@@ -350,7 +341,7 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu
350341
for (i in 0..< uploadedParts.size - 1) {
351342
val part = uploadedParts[i]
352343
verifyPartNumberLimits(part.partNumber.toString())
353-
if (part.size == null || part.size < MINIMUM_PART_SIZE) {
344+
if (part.size < MINIMUM_PART_SIZE) {
354345
LOG.error(
355346
"Multipart part size too small. bucket={}, id={}, uploadId={}, size={}",
356347
bucketMetadata, id, uploadId, part.size

server/src/main/kotlin/com/adobe/testing/s3mock/service/ObjectService.kt

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import java.io.InputStream
4040
import java.nio.file.Path
4141
import java.time.Instant
4242
import java.time.temporal.ChronoUnit
43-
import java.util.regex.Pattern
4443

4544
open class ObjectService(private val bucketStore: BucketStore, private val objectStore: ObjectStore) : ServiceBase() {
4645
fun copyS3Object(
@@ -180,7 +179,7 @@ open class ObjectService(private val bucketStore: BucketStore, private val objec
180179
}
181180

182181
private fun verifyTagChars(tag: String) {
183-
if (!TAG_ALLOWED_CHARS.matcher(tag).matches()) throw S3Exception.INVALID_TAG
182+
if (!TAG_ALLOWED_CHARS.matches(tag)) throw S3Exception.INVALID_TAG
184183
}
185184

186185
fun setLegalHold(bucketName: String, key: String, versionId: String?, legalHold: LegalHold) {
@@ -213,8 +212,8 @@ open class ObjectService(private val bucketStore: BucketStore, private val objec
213212

214213
fun verifyMd5(input: Path, contentMd5: String?) {
215214
try {
216-
input.toFile().inputStream().use { stream ->
217-
verifyMd5(stream, contentMd5)
215+
input.toFile().inputStream().use {
216+
verifyMd5(it, contentMd5)
218217
}
219218
} catch (_: IOException) {
220219
throw S3Exception.BAD_REQUEST_CONTENT
@@ -270,16 +269,16 @@ open class ObjectService(private val bucketStore: BucketStore, private val objec
270269
verifyObjectMatching(match, null, null, null, s3ObjectMetadata)
271270
s3ObjectMetadata ?: return
272271

273-
matchLastModifiedTime?.firstOrNull()?.let { expected ->
272+
matchLastModifiedTime?.firstOrNull()?.let {
274273
val lastModified = Instant.ofEpochMilli(s3ObjectMetadata.lastModified)
275-
if (!lastModified.truncatedTo(ChronoUnit.SECONDS).equals(expected.truncatedTo(ChronoUnit.SECONDS))) {
274+
if (!lastModified.truncatedTo(ChronoUnit.SECONDS).equals(it.truncatedTo(ChronoUnit.SECONDS))) {
276275
throw S3Exception.PRECONDITION_FAILED
277276
}
278277
}
279278

280-
matchSize?.firstOrNull()?.let { expected ->
279+
matchSize?.firstOrNull()?.let {
281280
val size = s3ObjectMetadata.size.toLong()
282-
if (size != expected) throw S3Exception.PRECONDITION_FAILED
281+
if (size != it) throw S3Exception.PRECONDITION_FAILED
283282
}
284283
}
285284

@@ -300,9 +299,9 @@ open class ObjectService(private val bucketStore: BucketStore, private val objec
300299
val etag = normalizeEtag(s3ObjectMetadata.etag)
301300
val lastModified = Instant.ofEpochMilli(s3ObjectMetadata.lastModified)
302301

303-
ifModifiedSince?.firstOrNull()?.let { ims ->
304-
if (ims.isAfter(lastModified)) {
305-
LOG.debug("Object {} not modified since {}", s3ObjectMetadata.key, ims)
302+
ifModifiedSince?.firstOrNull()?.let {
303+
if (it.isAfter(lastModified)) {
304+
LOG.debug("Object {} not modified since {}", s3ObjectMetadata.key, it)
306305
throw S3Exception.NOT_MODIFIED
307306
}
308307
}
@@ -318,21 +317,17 @@ open class ObjectService(private val bucketStore: BucketStore, private val objec
318317
}
319318
}
320319

321-
ifUnmodifiedSince?.firstOrNull()?.let { ius ->
322-
if (ius.isBefore(lastModified)) {
323-
LOG.debug("Object {} modified since {}", s3ObjectMetadata.key, ius)
320+
ifUnmodifiedSince?.firstOrNull()?.let {
321+
if (it.isBefore(lastModified)) {
322+
LOG.debug("Object {} modified since {}", s3ObjectMetadata.key, it)
324323
throw S3Exception.PRECONDITION_FAILED
325324
}
326325
}
327326

328327
if (!noneMatch.isNullOrEmpty()) {
329328
if (noneMatch.contains(WILDCARD_ETAG) || noneMatch.contains(WILDCARD) || noneMatch.contains(etag)) {
330329
// request cares only that the object etag does not match.
331-
LOG.debug(
332-
"Object {} has an ETag {} that matches one of the 'noneMatch' values",
333-
s3ObjectMetadata.key,
334-
etag
335-
)
330+
LOG.debug("Object {} has an ETag {} that matches one of the 'noneMatch' values", s3ObjectMetadata.key, etag)
336331
throw S3Exception.NOT_MODIFIED
337332
}
338333
}
@@ -365,7 +360,7 @@ open class ObjectService(private val bucketStore: BucketStore, private val objec
365360
const val WILDCARD_ETAG: String = "\"*\""
366361
const val WILDCARD: String = "*"
367362
private val LOG: Logger = LoggerFactory.getLogger(ObjectService::class.java)
368-
private val TAG_ALLOWED_CHARS: Pattern = Pattern.compile("[\\w+ \\-=.:/@]*")
363+
private val TAG_ALLOWED_CHARS: Regex = Regex("[\\w+ \\-=.:/@]*")
369364
private const val MAX_ALLOWED_TAGS = 50
370365
private const val MIN_ALLOWED_TAG_KEY_LENGTH = 1
371366
private const val MAX_ALLOWED_TAG_KEY_LENGTH = 128

0 commit comments

Comments
 (0)