This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new ebded1d42c JAMES-3925 Rework JMAP quota cleanup
ebded1d42c is described below

commit ebded1d42c91af4ccd20179f92302c34cdbd8383
Author: Tung Tran <[email protected]>
AuthorDate: Fri Aug 25 15:16:20 2023 +0700

    JAMES-3925 Rework JMAP quota cleanup
---
 .../james/modules/data/CassandraJmapModule.java    |  2 -
 .../jmap/cassandra/upload/BucketNameGenerator.java | 54 ---------------
 .../upload/CassandraUploadRepository.java          | 36 +++++-----
 .../jmap/cassandra/upload/UploadConfiguration.java | 61 -----------------
 .../james/jmap/cassandra/upload/UploadDAO.java     | 33 ++++-----
 .../james/jmap/cassandra/upload/UploadModule.java  | 13 +---
 .../cassandra/upload/BucketNameGeneratorTest.java  | 67 ------------------
 .../upload/CassandraUploadRepositoryTest.java      | 24 +------
 .../webadmin/data/jmap/JmapUploadRoutesTest.java   | 80 ++++++++++++----------
 9 files changed, 80 insertions(+), 290 deletions(-)

diff --git 
a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
 
b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
index a6c0884c27..f6a1310753 100644
--- 
a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
+++ 
b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
@@ -59,7 +59,6 @@ import 
org.apache.james.jmap.cassandra.pushsubscription.CassandraPushSubscriptio
 import 
org.apache.james.jmap.cassandra.pushsubscription.CassandraPushSubscriptionRepository;
 import org.apache.james.jmap.cassandra.upload.CassandraUploadRepository;
 import org.apache.james.jmap.cassandra.upload.CassandraUploadUsageRepository;
-import org.apache.james.jmap.cassandra.upload.UploadConfiguration;
 import org.apache.james.jmap.cassandra.upload.UploadDAO;
 import org.apache.james.jmap.cassandra.upload.UploadModule;
 import org.apache.james.user.api.DeleteUserDataTaskStep;
@@ -83,7 +82,6 @@ public class CassandraJmapModule extends AbstractModule {
         bind(UploadDAO.class).in(Scopes.SINGLETON);
         bind(UploadRepository.class).to(CassandraUploadRepository.class);
         
bind(UploadUsageRepository.class).to(CassandraUploadUsageRepository.class);
-        
bind(UploadConfiguration.class).toInstance(UploadConfiguration.SINGLETON);
 
         bind(CassandraCustomIdentityDAO.class).in(Scopes.SINGLETON);
         bind(CustomIdentityDAO.class).to(CassandraCustomIdentityDAO.class);
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/BucketNameGenerator.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/BucketNameGenerator.java
deleted file mode 100644
index b96dc458a6..0000000000
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/BucketNameGenerator.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.jmap.cassandra.upload;
-
-import java.time.Clock;
-import java.time.LocalDate;
-import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
-import java.util.function.Predicate;
-
-import javax.inject.Inject;
-
-public class BucketNameGenerator {
-    private final Clock clock;
-
-    @Inject
-    public BucketNameGenerator(Clock clock) {
-        this.clock = clock;
-    }
-
-    public UploadBucketName current() {
-        int weekCount = currentWeekCount();
-        return new UploadBucketName(weekCount);
-    }
-
-    public Predicate<UploadBucketName> evictionPredicate() {
-        final int currentWeekCount = currentWeekCount();
-
-        return uploadBucketName -> uploadBucketName.getWeekNumber() < 
currentWeekCount - 1;
-    }
-
-    private int currentWeekCount() {
-        return Math.toIntExact(ChronoUnit.WEEKS.between(
-            LocalDate.ofEpochDay(0),
-            LocalDate.ofInstant(clock.instant(), ZoneOffset.UTC)));
-    }
-}
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepository.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepository.java
index 5d525d89b2..d890284126 100644
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepository.java
+++ 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepository.java
@@ -19,9 +19,12 @@
 package org.apache.james.jmap.cassandra.upload;
 
 import static org.apache.james.blob.api.BlobStore.StoragePolicy.LOW_COST;
+import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
 import java.io.InputStream;
 import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
 
 import javax.inject.Inject;
 
@@ -34,7 +37,6 @@ import org.apache.james.jmap.api.model.UploadMetaData;
 import org.apache.james.jmap.api.model.UploadNotFoundException;
 import org.apache.james.jmap.api.upload.UploadRepository;
 import org.apache.james.mailbox.model.ContentType;
-import org.reactivestreams.Publisher;
 
 import com.datastax.oss.driver.api.core.uuid.Uuids;
 import com.google.common.io.CountingInputStream;
@@ -43,56 +45,56 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class CassandraUploadRepository implements UploadRepository {
+
+    public static final BucketName UPLOAD_BUCKET = 
BucketName.of("jmap-uploads");
+    public static final Duration EXPIRE_DURATION = Duration.ofDays(7);
     private final UploadDAO uploadDAO;
     private final BlobStore blobStore;
-    private final BucketNameGenerator bucketNameGenerator;
     private final Clock clock;
 
     @Inject
-    public CassandraUploadRepository(UploadDAO uploadDAO, BlobStore blobStore, 
BucketNameGenerator bucketNameGenerator, Clock clock) {
+    public CassandraUploadRepository(UploadDAO uploadDAO, BlobStore blobStore, 
Clock clock) {
         this.uploadDAO = uploadDAO;
         this.blobStore = blobStore;
-        this.bucketNameGenerator = bucketNameGenerator;
         this.clock = clock;
     }
 
     @Override
-    public Publisher<UploadMetaData> upload(InputStream data, ContentType 
contentType, Username user) {
+    public Mono<UploadMetaData> upload(InputStream data, ContentType 
contentType, Username user) {
         UploadId uploadId = generateId();
-        UploadBucketName uploadBucketName = bucketNameGenerator.current();
-        BucketName bucketName = uploadBucketName.asBucketName();
 
         return Mono.fromCallable(() -> new CountingInputStream(data))
-            .flatMap(countingInputStream -> 
Mono.from(blobStore.save(bucketName, countingInputStream, LOW_COST))
-                .map(blobId -> new UploadDAO.UploadRepresentation(uploadId, 
bucketName, blobId, contentType, countingInputStream.getCount(), user, 
clock.instant()))
+            .flatMap(countingInputStream -> 
Mono.from(blobStore.save(UPLOAD_BUCKET, countingInputStream, LOW_COST))
+                .map(blobId -> new UploadDAO.UploadRepresentation(uploadId, 
blobId, contentType, countingInputStream.getCount(), user, clock.instant()))
                 .flatMap(upload -> uploadDAO.save(upload)
                     .thenReturn(upload.toUploadMetaData())));
     }
 
     @Override
-    public Publisher<Upload> retrieve(UploadId id, Username user) {
+    public Mono<Upload> retrieve(UploadId id, Username user) {
         return uploadDAO.retrieve(user, id)
             .map(upload -> Upload.from(upload.toUploadMetaData(),
-                () -> blobStore.read(upload.getBucketName(), 
upload.getBlobId(), LOW_COST)))
+                () -> blobStore.read(UPLOAD_BUCKET, upload.getBlobId(), 
LOW_COST)))
             .switchIfEmpty(Mono.error(() -> new UploadNotFoundException(id)));
     }
 
     @Override
-    public Publisher<Void> delete(UploadId id, Username user) {
+    public Mono<Void> delete(UploadId id, Username user) {
         return uploadDAO.delete(user, id);
     }
 
     @Override
-    public Publisher<UploadMetaData> listUploads(Username user) {
+    public Flux<UploadMetaData> listUploads(Username user) {
         return uploadDAO.list(user)
             .map(UploadDAO.UploadRepresentation::toUploadMetaData);
     }
 
     public Mono<Void> purge() {
-        return Flux.from(blobStore.listBuckets())
-            .<UploadBucketName>handle((bucketName, sink) -> 
UploadBucketName.ofBucket(bucketName).ifPresentOrElse(sink::next, 
sink::complete))
-            .filter(bucketNameGenerator.evictionPredicate())
-            .concatMap(bucket -> blobStore.deleteBucket(bucket.asBucketName()))
+        Instant sevenDaysAgo = clock.instant().minus(EXPIRE_DURATION);
+        return Flux.from(uploadDAO.all())
+            .filter(upload -> upload.getUploadDate().isBefore(sevenDaysAgo))
+            .flatMap(upload -> Mono.from(blobStore.delete(UPLOAD_BUCKET, 
upload.getBlobId()))
+                .then(uploadDAO.delete(upload.getUser(), upload.getId())), 
DEFAULT_CONCURRENCY)
             .then();
     }
 
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadConfiguration.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadConfiguration.java
deleted file mode 100644
index f54d24ddf7..0000000000
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadConfiguration.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.jmap.cassandra.upload;
-
-import java.time.Duration;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
-
-public class UploadConfiguration {
-    public static final UploadConfiguration SINGLETON = new 
UploadConfiguration(Duration.ofDays(7));
-
-    private final Duration uploadTtlDuration;
-
-    public UploadConfiguration(Duration uploadTtlDuration) {
-        this.uploadTtlDuration = uploadTtlDuration;
-    }
-
-    public Duration getUploadTtlDuration() {
-        return uploadTtlDuration;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof UploadConfiguration) {
-            UploadConfiguration other = (UploadConfiguration) obj;
-            return Objects.equal(uploadTtlDuration, other.uploadTtlDuration);
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(uploadTtlDuration);
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects
-            .toStringHelper(this)
-            .add("uploadTtlDuration", uploadTtlDuration)
-            .toString();
-    }
-}
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadDAO.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadDAO.java
index 8b7c04133f..2f74a21b15 100644
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadDAO.java
+++ 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadDAO.java
@@ -24,7 +24,6 @@ import static 
com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom;
 import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto;
 import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom;
 import static org.apache.james.jmap.cassandra.upload.UploadModule.BLOB_ID;
-import static org.apache.james.jmap.cassandra.upload.UploadModule.BUCKET_ID;
 import static org.apache.james.jmap.cassandra.upload.UploadModule.CONTENT_TYPE;
 import static org.apache.james.jmap.cassandra.upload.UploadModule.ID;
 import static org.apache.james.jmap.cassandra.upload.UploadModule.SIZE;
@@ -40,7 +39,6 @@ import javax.inject.Inject;
 
 import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
 import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BucketName;
 import org.apache.james.core.Username;
 import org.apache.james.jmap.api.model.UploadId;
 import org.apache.james.jmap.api.model.UploadMetaData;
@@ -59,18 +57,16 @@ import reactor.core.publisher.Mono;
 public class UploadDAO {
     public static class UploadRepresentation {
         private final UploadId id;
-        private final BucketName bucketName;
         private final BlobId blobId;
         private final ContentType contentType;
         private final long size;
         private final Username user;
         private final Instant uploadDate;
 
-        public UploadRepresentation(UploadId id, BucketName bucketName, BlobId 
blobId, ContentType contentType, long size, Username user, Instant uploadDate) {
+        public UploadRepresentation(UploadId id, BlobId blobId, ContentType 
contentType, long size, Username user, Instant uploadDate) {
             this.user = user;
             Preconditions.checkArgument(size >= 0, "Size must be strictly 
positive");
             this.id = id;
-            this.bucketName = bucketName;
             this.blobId = blobId;
             this.contentType = contentType;
             this.size = size;
@@ -81,10 +77,6 @@ public class UploadDAO {
             return id;
         }
 
-        public BucketName getBucketName() {
-            return bucketName;
-        }
-
         public BlobId getBlobId() {
             return blobId;
         }
@@ -114,7 +106,6 @@ public class UploadDAO {
             if (obj instanceof UploadRepresentation) {
                 UploadRepresentation other = (UploadRepresentation) obj;
                 return Objects.equal(id, other.id)
-                    && Objects.equal(bucketName, other.bucketName)
                     && Objects.equal(user, other.user)
                     && Objects.equal(blobId, other.blobId)
                     && Objects.equal(contentType, other.contentType)
@@ -126,7 +117,7 @@ public class UploadDAO {
 
         @Override
         public int hashCode() {
-            return Objects.hashCode(id, bucketName, blobId, contentType, size, 
user, uploadDate);
+            return Objects.hashCode(id, blobId, contentType, size, user, 
uploadDate);
         }
 
         @Override
@@ -134,7 +125,6 @@ public class UploadDAO {
             return MoreObjects
                 .toStringHelper(this)
                 .add("id", id)
-                .add("bucketName", bucketName)
                 .add("blobId", blobId)
                 .add("contentType", contentType)
                 .add("user", user)
@@ -150,22 +140,21 @@ public class UploadDAO {
     private final PreparedStatement insert;
     private final PreparedStatement selectOne;
     private final PreparedStatement delete;
-
     private final PreparedStatement list;
 
+    private final PreparedStatement all;
+
     @Inject
-    public UploadDAO(CqlSession session, BlobId.Factory blobIdFactory, 
UploadConfiguration configuration) {
+    public UploadDAO(CqlSession session, BlobId.Factory blobIdFactory) {
         this.executor = new CassandraAsyncExecutor(session);
         this.blobIdFactory = blobIdFactory;
         this.insert = session.prepare(insertInto(TABLE_NAME)
             .value(ID, bindMarker(ID))
-            .value(BUCKET_ID, bindMarker(BUCKET_ID))
             .value(BLOB_ID, bindMarker(BLOB_ID))
             .value(SIZE, bindMarker(SIZE))
             .value(USER, bindMarker(USER))
             .value(CONTENT_TYPE, bindMarker(CONTENT_TYPE))
             .value(UPLOAD_DATE, bindMarker(UPLOAD_DATE))
-            .usingTtl((int) configuration.getUploadTtlDuration().getSeconds())
             .build());
 
         this.list = session.prepare(selectFrom(TABLE_NAME)
@@ -183,13 +172,17 @@ public class UploadDAO {
             .whereColumn(USER).isEqualTo(bindMarker(USER))
             .whereColumn(ID).isEqualTo(bindMarker(ID))
             .build());
+
+        this.all = session.prepare(selectFrom(TABLE_NAME)
+            .all()
+            .allowFiltering()
+            .build());
     }
 
     public Mono<Void> save(UploadRepresentation uploadRepresentation) {
         return executor.executeVoid(insert.bind()
             .setString(USER, uploadRepresentation.getUser().asString())
             .setUuid(ID, uploadRepresentation.getId().getId())
-            .setString(BUCKET_ID, 
uploadRepresentation.getBucketName().asString())
             .setString(BLOB_ID, uploadRepresentation.getBlobId().asString())
             .setLong(SIZE, uploadRepresentation.getSize())
             .setInstant(UPLOAD_DATE, uploadRepresentation.getUploadDate())
@@ -215,9 +208,13 @@ public class UploadDAO {
             .setUuid(ID, uploadId.getId()));
     }
 
+    public Flux<UploadRepresentation> all() {
+        return Flux.from(executor.executeRows(all.bind()))
+            .map(rowToUploadRepresentation());
+    }
+
     private Function<Row, UploadRepresentation> rowToUploadRepresentation() {
         return row -> new UploadRepresentation(UploadId.from(row.getUuid(ID)),
-            BucketName.of(row.getString(BUCKET_ID)),
             blobIdFactory.from(row.getString(BLOB_ID)),
             ContentType.of(row.getString(CONTENT_TYPE)),
             row.getLong(SIZE),
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadModule.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadModule.java
index eeb166a712..8711a7a608 100644
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadModule.java
+++ 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/UploadModule.java
@@ -19,40 +19,29 @@
 
 package org.apache.james.jmap.cassandra.upload;
 
-import static 
com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition.rows;
-import static 
com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy.CompactionWindowUnit.DAYS;
-import static 
org.apache.james.backends.cassandra.utils.CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION;
-
 import org.apache.james.backends.cassandra.components.CassandraModule;
 
 import com.datastax.oss.driver.api.core.CqlIdentifier;
 import com.datastax.oss.driver.api.core.type.DataTypes;
-import com.datastax.oss.driver.api.querybuilder.SchemaBuilder;
 
 public interface UploadModule {
 
-    String TABLE_NAME = "uploads_2";
+    String TABLE_NAME = "uploadsV2";
 
     CqlIdentifier ID = CqlIdentifier.fromCql("id");
     CqlIdentifier CONTENT_TYPE = CqlIdentifier.fromCql("content_type");
     CqlIdentifier SIZE = CqlIdentifier.fromCql("size");
-    CqlIdentifier BUCKET_ID = CqlIdentifier.fromCql("bucket_id");
     CqlIdentifier BLOB_ID = CqlIdentifier.fromCql("blob_id");
     CqlIdentifier USER = CqlIdentifier.fromCql("user");
     CqlIdentifier UPLOAD_DATE = CqlIdentifier.fromCql("upload_date");
 
     CassandraModule MODULE = CassandraModule.table(TABLE_NAME)
         .comment("Holds JMAP uploads")
-        .options(options -> options
-            .withCompaction(SchemaBuilder.timeWindowCompactionStrategy()
-                .withCompactionWindow(7, DAYS))
-            .withCaching(true, rows(DEFAULT_CACHED_ROW_PER_PARTITION)))
         .statement(statement -> types -> statement
             .withPartitionKey(USER, DataTypes.TEXT)
             .withClusteringColumn(ID, DataTypes.TIMEUUID)
             .withColumn(CONTENT_TYPE, DataTypes.TEXT)
             .withColumn(SIZE, DataTypes.BIGINT)
-            .withColumn(BUCKET_ID, DataTypes.TEXT)
             .withColumn(BLOB_ID, DataTypes.TEXT)
             .withColumn(UPLOAD_DATE, DataTypes.TIMESTAMP))
         .build();
diff --git 
a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/upload/BucketNameGeneratorTest.java
 
b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/upload/BucketNameGeneratorTest.java
deleted file mode 100644
index ee3d6388d0..0000000000
--- 
a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/upload/BucketNameGeneratorTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.jmap.cassandra.upload;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.time.Clock;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-
-import org.junit.jupiter.api.Test;
-
-class BucketNameGeneratorTest {
-    ZonedDateTime NOW = ZonedDateTime.parse("2015-10-30T14:12:00Z");
-
-    @Test
-    void currentShouldReturnCorrectValue() {
-        BucketNameGenerator generator = new 
BucketNameGenerator(Clock.fixed(NOW.toInstant(), ZoneOffset.UTC));
-
-        assertThat(generator.current()).isEqualTo(new UploadBucketName(2391));
-    }
-
-    @Test
-    void evictionPredicateShouldKeepPresentBucket() {
-        BucketNameGenerator generator = new 
BucketNameGenerator(Clock.fixed(NOW.toInstant(), ZoneOffset.UTC));
-
-        assertThat(generator.evictionPredicate().test(new 
UploadBucketName(2391))).isFalse();
-    }
-
-    @Test
-    void evictionPredicateShouldKeepFutureBuckets() {
-        BucketNameGenerator generator = new 
BucketNameGenerator(Clock.fixed(NOW.toInstant(), ZoneOffset.UTC));
-
-        assertThat(generator.evictionPredicate().test(new 
UploadBucketName(2392))).isFalse();
-    }
-
-    @Test
-    void evictionPredicateShouldKeepRecentBuckets() {
-        BucketNameGenerator generator = new 
BucketNameGenerator(Clock.fixed(NOW.toInstant(), ZoneOffset.UTC));
-
-        assertThat(generator.evictionPredicate().test(new 
UploadBucketName(2390))).isFalse();
-    }
-
-    @Test
-    void evictionPredicateShouldMatchOldBuckets() {
-        BucketNameGenerator generator = new 
BucketNameGenerator(Clock.fixed(NOW.toInstant(), ZoneOffset.UTC));
-
-        assertThat(generator.evictionPredicate().test(new 
UploadBucketName(2389))).isTrue();
-    }
-}
\ No newline at end of file
diff --git 
a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepositoryTest.java
 
b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepositoryTest.java
index bcab9871dd..e5cf9403be 100644
--- 
a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepositoryTest.java
+++ 
b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/upload/CassandraUploadRepositoryTest.java
@@ -19,30 +19,21 @@
 
 package org.apache.james.jmap.cassandra.upload;
 
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
 import java.time.Clock;
-import java.time.Duration;
 
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.memory.MemoryBlobStoreDAO;
-import org.apache.james.core.Username;
 import org.apache.james.jmap.api.model.UploadId;
-import org.apache.james.jmap.api.model.UploadNotFoundException;
 import org.apache.james.jmap.api.upload.UploadRepository;
 import org.apache.james.jmap.api.upload.UploadRepositoryContract;
-import org.apache.james.mailbox.model.ContentType;
 import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 import com.datastax.oss.driver.api.core.uuid.Uuids;
 
-import reactor.core.publisher.Mono;
-
 class CassandraUploadRepositoryTest implements UploadRepositoryContract {
     @RegisterExtension
     static CassandraClusterExtension cassandra = new 
CassandraClusterExtension(UploadModule.MODULE);
@@ -52,9 +43,8 @@ class CassandraUploadRepositoryTest implements 
UploadRepositoryContract {
     void setUp() {
         Clock clock = Clock.systemUTC();
         testee = new CassandraUploadRepository(new 
UploadDAO(cassandra.getCassandraCluster().getConf(),
-            new HashBlobId.Factory(),
-            new UploadConfiguration(Duration.ofSeconds(5))), new 
DeDuplicationBlobStore(new MemoryBlobStoreDAO(), BucketName.of("default"), new 
HashBlobId.Factory()),
-            new BucketNameGenerator(clock), clock);
+            new HashBlobId.Factory()), new DeDuplicationBlobStore(new 
MemoryBlobStoreDAO(), BucketName.of("default"), new HashBlobId.Factory()),
+            clock);
     }
 
     @Override
@@ -67,14 +57,4 @@ class CassandraUploadRepositoryTest implements 
UploadRepositoryContract {
         return testee;
     }
 
-    @Test
-    void uploadShouldExpire() throws Exception {
-        Username bob = Username.of("bob");
-        UploadId id = Mono.from(testee.upload(data(), 
ContentType.of("text/plain"), bob)).block().uploadId();
-
-        Thread.sleep(6000);
-
-        assertThatThrownBy(() -> Mono.from(testee.retrieve(id, 
bob)).blockOptional())
-            .isInstanceOf(UploadNotFoundException.class);
-    }
 }
\ No newline at end of file
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/JmapUploadRoutesTest.java
 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/JmapUploadRoutesTest.java
index 173864f655..267528ae2c 100644
--- 
a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/JmapUploadRoutesTest.java
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/JmapUploadRoutesTest.java
@@ -21,6 +21,7 @@ package org.apache.james.webadmin.data.jmap;
 
 import static io.restassured.RestAssured.given;
 import static io.restassured.http.ContentType.JSON;
+import static 
org.apache.james.jmap.cassandra.upload.CassandraUploadRepository.UPLOAD_BUCKET;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
 import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
@@ -29,11 +30,10 @@ import static org.hamcrest.Matchers.notNullValue;
 
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
-import java.time.Duration;
 import java.time.ZonedDateTime;
-import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.blob.api.BlobStore;
@@ -42,15 +42,14 @@ import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.memory.MemoryBlobStoreDAO;
 import org.apache.james.core.Username;
 import org.apache.james.jmap.api.model.UploadId;
+import org.apache.james.jmap.api.model.UploadMetaData;
 import org.apache.james.jmap.api.model.UploadNotFoundException;
-import org.apache.james.jmap.cassandra.upload.BucketNameGenerator;
 import org.apache.james.jmap.cassandra.upload.CassandraUploadRepository;
-import org.apache.james.jmap.cassandra.upload.UploadConfiguration;
 import org.apache.james.jmap.cassandra.upload.UploadDAO;
 import org.apache.james.jmap.cassandra.upload.UploadModule;
 import org.apache.james.json.DTOConverter;
 import org.apache.james.mailbox.model.ContentType;
-import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
+import org.apache.james.server.blob.deduplication.PassThroughBlobStore;
 import org.apache.james.task.Hostname;
 import org.apache.james.task.MemoryTaskManager;
 import org.apache.james.utils.UpdatableTickingClock;
@@ -67,7 +66,6 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
-import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
 
 import io.restassured.RestAssured;
 import reactor.core.publisher.Flux;
@@ -88,7 +86,6 @@ class JmapUploadRoutesTest {
     private WebAdminServer webAdminServer;
     private MemoryTaskManager taskManager;
     private BlobStore blobStore;
-    private BucketNameGenerator bucketNameGenerator;
     private CassandraUploadRepository cassandraUploadRepository;
     private UpdatableTickingClock clock;
 
@@ -100,16 +97,12 @@ class JmapUploadRoutesTest {
     void setUp() {
         taskManager = new MemoryTaskManager(new Hostname("foo"));
         clock = new UpdatableTickingClock(TIMESTAMP.toInstant());
-        bucketNameGenerator = new BucketNameGenerator(clock);
-        blobStore = new DeDuplicationBlobStore(new MemoryBlobStoreDAO(),
+        blobStore = new PassThroughBlobStore(new MemoryBlobStoreDAO(),
             BucketName.of("default"),
             new HashBlobId.Factory());
 
         cassandraUploadRepository = new CassandraUploadRepository(new 
UploadDAO(cassandraCluster.getCassandraCluster().getConf(),
-            new HashBlobId.Factory(),
-            new UploadConfiguration(Duration.ofSeconds(5))),
-            blobStore,
-            bucketNameGenerator, clock);
+            new HashBlobId.Factory()), blobStore, clock);
 
         JsonTransformer jsonTransformer = new JsonTransformer();
         TasksRoutes tasksRoutes = new TasksRoutes(taskManager, 
jsonTransformer, 
DTOConverter.of(UploadCleanupTaskAdditionalInformationDTO.SERIALIZATION_MODULE));
@@ -198,10 +191,8 @@ class JmapUploadRoutesTest {
     }
 
     @Test
-    void cleanUploadTaskShouldRemoveExpiredBucket() {
-        BucketName expiredBucket = 
bucketNameGenerator.current().asBucketName();
-
-        Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
+    void cleanUploadTaskShouldRemoveExpiredBlob() {
+        UploadMetaData uploadMetaData = 
Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
 
         clock.setInstant(TIMESTAMP.plusWeeks(3).toInstant());
 
@@ -216,15 +207,13 @@ class JmapUploadRoutesTest {
         .when()
             .get(taskId + "/await");
 
-        assertThat(Flux.from(blobStore.listBuckets()).collectList().block())
-            .doesNotContain(expiredBucket);
+        
assertThat(Flux.from(blobStore.listBlobs(UPLOAD_BUCKET)).collectList().block())
+            .doesNotContain(uploadMetaData.blobId());
     }
 
     @Test
-    void cleanUploadTaskShouldNotRemoveUnExpiredBucket() {
-        BucketName unExpiredBucket = 
bucketNameGenerator.current().asBucketName();
-
-        Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
+    void cleanUploadTaskShouldNotRemoveUnExpiredBlob() {
+        UploadMetaData upload = 
Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
 
         String taskId = given()
             .queryParam("scope", "expired")
@@ -237,26 +226,41 @@ class JmapUploadRoutesTest {
         .when()
             .get(taskId + "/await");
 
-        assertThat(Flux.from(blobStore.listBuckets()).collectList().block())
-            .contains(unExpiredBucket);
+        
assertThat(Flux.from(blobStore.listBlobs(UPLOAD_BUCKET)).collectList().block())
+            .containsOnly(upload.blobId());
+    }
+
+    @Test
+    void cleanUploadTaskShouldNotRemoveUnExpiredUpload() {
+        UploadMetaData upload = 
Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
+
+        String taskId = given()
+            .queryParam("scope", "expired")
+            .delete()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+            .when()
+            .get(taskId + "/await");
+
+        
assertThat(cassandraUploadRepository.listUploads(USERNAME).collectList().block())
+            .containsOnly(upload);
     }
 
     @Test
     void cleanUploadTaskShouldSuccessWhenMixCase() {
-        BucketName expiredBucketName1 = 
bucketNameGenerator.current().asBucketName();
-        Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
+        UploadMetaData upload1 = 
Mono.from(cassandraUploadRepository.upload(IOUtils.toInputStream("DATA 1", 
StandardCharsets.UTF_8), CONTENT_TYPE, USERNAME)).block();
 
         clock.setInstant(TIMESTAMP.plusWeeks(1).toInstant());
-        BucketName expiredBucketName2 = 
bucketNameGenerator.current().asBucketName();
-        Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
+        UploadMetaData upload2 = 
Mono.from(cassandraUploadRepository.upload(IOUtils.toInputStream("DATA 2", 
StandardCharsets.UTF_8), CONTENT_TYPE, USERNAME)).block();
 
         clock.setInstant(TIMESTAMP.plusWeeks(3).toInstant());
-        BucketName unExpiredBucketName1 = 
bucketNameGenerator.current().asBucketName();
-        Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
+        UploadMetaData upload3 = 
Mono.from(cassandraUploadRepository.upload(IOUtils.toInputStream("DATA 3", 
StandardCharsets.UTF_8), CONTENT_TYPE, USERNAME)).block();
 
         clock.setInstant(TIMESTAMP.plusWeeks(4).toInstant());
-        BucketName unExpiredBucketName2 = 
bucketNameGenerator.current().asBucketName();
-        Mono.from(cassandraUploadRepository.upload(DATA, CONTENT_TYPE, 
USERNAME)).block();
+        UploadMetaData upload4 = 
Mono.from(cassandraUploadRepository.upload(IOUtils.toInputStream("DATA 4", 
StandardCharsets.UTF_8), CONTENT_TYPE, USERNAME)).block();
 
         String taskId = given()
             .queryParam("scope", "expired")
@@ -269,11 +273,13 @@ class JmapUploadRoutesTest {
         .when()
             .get(taskId + "/await");
 
-        List<BucketName> bucketNameList = 
Flux.from(blobStore.listBuckets()).collectList().block();
+        
assertThat(cassandraUploadRepository.listUploads(USERNAME).collectList().block())
+            .doesNotContain(upload1, upload2)
+            .contains(upload3, upload4);
 
-        assertThat(bucketNameList)
-            .contains(unExpiredBucketName1, unExpiredBucketName2)
-            .doesNotContain(expiredBucketName1, expiredBucketName2);
+        
assertThat(Flux.from(blobStore.listBlobs(UPLOAD_BUCKET)).collectList().block())
+            .doesNotContain(upload1.blobId(), upload2.blobId())
+            .contains(upload3.blobId(), upload4.blobId());
     }
 
     @Test


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to