This is an automated email from the ASF dual-hosted git repository. rouazana pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 9850741c13a24173783e04a20a8f658c61e9a7b6 Author: Tran Tien Duc <[email protected]> AuthorDate: Fri Jul 19 11:32:53 2019 +0700 JAMES-2835 Handling vault.loadMimeMessage() content not found exception The callers like ExportService, RestoreService can decide how to handle the case when content of the deleted message cannot be found. --- .../DeletedMessageContentNotFoundException.java | 38 ++++++++++++++++++++++ .../vault/blob/BlobStoreDeletedMessageVault.java | 11 ++++++- .../james/blob/api/ObjectNotFoundException.java | 31 ++++++++++++++++++ .../apache/james/blob/api/BlobStoreContract.java | 4 +-- .../james/blob/cassandra/CassandraBlobsDAO.java | 4 +-- .../apache/james/blob/memory/MemoryBlobStore.java | 4 +-- .../blob/objectstorage/ObjectStorageBlobsDAO.java | 3 +- .../james/webadmin/vault/routes/ExportService.java | 20 +++++++++++- .../webadmin/vault/routes/RestoreService.java | 13 ++++++++ 9 files changed, 119 insertions(+), 9 deletions(-) diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageContentNotFoundException.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageContentNotFoundException.java new file mode 100644 index 0000000..f8d5cd7 --- /dev/null +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageContentNotFoundException.java @@ -0,0 +1,38 @@ +/**************************************************************** + * 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.vault; + +import org.apache.james.core.User; +import org.apache.james.mailbox.model.MessageId; + +public class DeletedMessageContentNotFoundException extends RuntimeException { + + public DeletedMessageContentNotFoundException(User user, MessageId messageId) { + this("cannot find mime message associated with id " + messageId.serialize() + " and user " + user.asString()); + } + + public DeletedMessageContentNotFoundException(String message) { + super(message); + } + + public DeletedMessageContentNotFoundException(String message, Throwable cause) { + super(message,cause); + } +} diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java index 0e92855..2efde1b 100644 --- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java @@ -28,10 +28,12 @@ import javax.inject.Inject; import org.apache.james.blob.api.BlobStore; import org.apache.james.blob.api.BucketName; +import org.apache.james.blob.api.ObjectNotFoundException; import org.apache.james.core.User; import org.apache.james.mailbox.model.MessageId; import org.apache.james.task.Task; import org.apache.james.vault.DeletedMessage; +import org.apache.james.vault.DeletedMessageContentNotFoundException; import org.apache.james.vault.DeletedMessageVault; import org.apache.james.vault.RetentionConfiguration; import org.apache.james.vault.metadata.DeletedMessageMetadataVault; @@ -86,7 +88,14 @@ public class BlobStoreDeletedMessageVault implements DeletedMessageVault { Preconditions.checkNotNull(user); Preconditions.checkNotNull(messageId); return Mono.from(messageMetadataVault.retrieveStorageInformation(user, messageId)) - .map(storageInformation -> blobStore.read(storageInformation.getBucketName(), storageInformation.getBlobId())); + .flatMap(storageInformation -> loadMimeMessage(storageInformation, user, messageId)); + } + + private Mono<InputStream> loadMimeMessage(StorageInformation storageInformation, User user, MessageId messageId) { + return Mono.fromSupplier(() -> blobStore.read(storageInformation.getBucketName(), storageInformation.getBlobId())) + .onErrorResume( + ObjectNotFoundException.class, + ex -> Mono.error(new DeletedMessageContentNotFoundException(user, messageId))); } @Override diff --git a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/ObjectNotFoundException.java b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/ObjectNotFoundException.java new file mode 100644 index 0000000..058287c --- /dev/null +++ b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/ObjectNotFoundException.java @@ -0,0 +1,31 @@ +/**************************************************************** + * 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.blob.api; + +public class ObjectNotFoundException extends ObjectStoreException { + + public ObjectNotFoundException(String message) { + super(message); + } + + public ObjectNotFoundException(String message, Throwable cause) { + super(message,cause); + } +} diff --git a/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobStoreContract.java b/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobStoreContract.java index 1b25ada..3c89b9c 100644 --- a/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobStoreContract.java +++ b/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobStoreContract.java @@ -141,7 +141,7 @@ public interface BlobStoreContract { BucketName defaultBucketName = store.getDefaultBucketName(); assertThatThrownBy(() -> store.readBytes(defaultBucketName, blobIdFactory().from("unknown")).block()) - .isExactlyInstanceOf(ObjectStoreException.class); + .isExactlyInstanceOf(ObjectNotFoundException.class); } @Test @@ -186,7 +186,7 @@ public interface BlobStoreContract { BucketName defaultBucketName = store.getDefaultBucketName(); assertThatThrownBy(() -> store.read(defaultBucketName, blobIdFactory().from("unknown"))) - .isInstanceOf(ObjectStoreException.class); + .isInstanceOf(ObjectNotFoundException.class); } @Test diff --git a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobsDAO.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobsDAO.java index 29cef15..9f5660c 100644 --- a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobsDAO.java +++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobsDAO.java @@ -42,7 +42,7 @@ import org.apache.james.blob.api.BlobId; import org.apache.james.blob.api.BlobStore; import org.apache.james.blob.api.BucketName; import org.apache.james.blob.api.HashBlobId; -import org.apache.james.blob.api.ObjectStoreException; +import org.apache.james.blob.api.ObjectNotFoundException; import org.apache.james.blob.cassandra.BlobTable.BlobParts; import org.apache.james.blob.cassandra.utils.DataChunker; import org.apache.james.blob.cassandra.utils.PipedStreamSubscriber; @@ -211,7 +211,7 @@ public class CassandraBlobsDAO implements BlobStore { Integer rowCount = selectRowCount(blobId) .publishOn(Schedulers.elastic()) .switchIfEmpty(Mono.error( - new ObjectStoreException(String.format("Could not retrieve blob metadata for %s", blobId)))) + new ObjectNotFoundException(String.format("Could not retrieve blob metadata for %s", blobId)))) .block(); return Flux.range(0, rowCount) .publishOn(Schedulers.elastic(), PREFETCH) diff --git a/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java b/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java index f10e75e..6d107ac 100644 --- a/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java +++ b/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java @@ -30,7 +30,7 @@ import org.apache.commons.io.IOUtils; import org.apache.james.blob.api.BlobId; import org.apache.james.blob.api.BlobStore; import org.apache.james.blob.api.BucketName; -import org.apache.james.blob.api.ObjectStoreException; +import org.apache.james.blob.api.ObjectNotFoundException; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -109,7 +109,7 @@ public class MemoryBlobStore implements BlobStore { private byte[] retrieveStoredValue(BucketName bucketName, BlobId blobId) { synchronized (blobs) { return Optional.ofNullable(blobs.get(bucketName, blobId)) - .orElseThrow(() -> new ObjectStoreException("Unable to find blob with id " + blobId + " in bucket " + bucketName.asString())); + .orElseThrow(() -> new ObjectNotFoundException("Unable to find blob with id " + blobId + " in bucket " + bucketName.asString())); } } diff --git a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java index 81acaeb..6352d07 100644 --- a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java +++ b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java @@ -31,6 +31,7 @@ import org.apache.commons.io.IOUtils; import org.apache.james.blob.api.BlobId; import org.apache.james.blob.api.BlobStore; import org.apache.james.blob.api.BucketName; +import org.apache.james.blob.api.ObjectNotFoundException; import org.apache.james.blob.api.ObjectStoreException; import org.apache.james.blob.objectstorage.aws.AwsS3AuthConfiguration; import org.apache.james.blob.objectstorage.aws.AwsS3ObjectStorage; @@ -167,7 +168,7 @@ public class ObjectStorageBlobsDAO implements BlobStore { if (blob != null) { return payloadCodec.read(new Payload(blob.getPayload(), Optional.empty())); } else { - throw new ObjectStoreException("fail to load blob with id " + blobId); + throw new ObjectNotFoundException("fail to load blob with id " + blobId); } } catch (IOException cause) { throw new ObjectStoreException( diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/ExportService.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/ExportService.java index 0ccef9f..56f254e 100644 --- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/ExportService.java +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/ExportService.java @@ -21,6 +21,7 @@ package org.apache.james.webadmin.vault.routes; import java.io.IOException; import java.util.Optional; +import java.util.function.Predicate; import javax.inject.Inject; @@ -32,9 +33,12 @@ import org.apache.james.blob.export.api.FileExtension; import org.apache.james.core.MailAddress; import org.apache.james.core.User; import org.apache.james.vault.DeletedMessage; +import org.apache.james.vault.DeletedMessageContentNotFoundException; import org.apache.james.vault.DeletedMessageVault; import org.apache.james.vault.DeletedMessageZipper; import org.apache.james.vault.search.Query; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.ByteSource; @@ -44,6 +48,11 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; class ExportService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExportService.class); + private static final Predicate<Throwable> CONTENT_NOT_FOUND_PREDICATE = + throwable -> throwable instanceof DeletedMessageContentNotFoundException; + private final BlobExportMechanism blobExport; private final BlobStore blobStore; private final DeletedMessageZipper zipper; @@ -81,7 +90,16 @@ class ExportService { } private DeletedMessageZipper.DeletedMessageContentLoader contentLoader(User user) { - return message -> Mono.from(vault.loadMimeMessage(user, message.getMessageId())).blockOptional(); + return message -> Mono.from(vault.loadMimeMessage(user, message.getMessageId())) + .onErrorResume(CONTENT_NOT_FOUND_PREDICATE, throwable -> { + LOGGER.info( + "Error happened when loading mime message associated with id {} of user {} in the vault", + message.getMessageId().serialize(), + user.asString(), + throwable); + return Mono.empty(); + }) + .blockOptional(); } private String exportMessage(User user) { diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java index 064a392..2cc36c9 100644 --- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java @@ -23,6 +23,8 @@ import static org.apache.james.mailbox.MessageManager.AppendCommand; import static org.apache.james.webadmin.vault.routes.RestoreService.RestoreResult.RESTORE_FAILED; import static org.apache.james.webadmin.vault.routes.RestoreService.RestoreResult.RESTORE_SUCCEED; +import java.util.function.Predicate; + import javax.inject.Inject; import org.apache.james.core.User; @@ -36,6 +38,7 @@ import org.apache.james.mailbox.model.ComposedMessageId; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.vault.DeletedMessage; +import org.apache.james.vault.DeletedMessageContentNotFoundException; import org.apache.james.vault.DeletedMessageVault; import org.apache.james.vault.search.Query; import org.slf4j.Logger; @@ -54,6 +57,8 @@ class RestoreService { } private static final Logger LOGGER = LoggerFactory.getLogger(RestoreService.class); + private static final Predicate<Throwable> CONTENT_NOT_FOUND_PREDICATE = + throwable -> throwable instanceof DeletedMessageContentNotFoundException; private final DeletedMessageVault deletedMessageVault; private final MailboxManager mailboxManager; @@ -86,6 +91,14 @@ class RestoreService { private Mono<AppendCommand> appendCommand(DeletedMessage deletedMessage) { return Mono.from(deletedMessageVault.loadMimeMessage(deletedMessage.getOwner(), deletedMessage.getMessageId())) + .onErrorResume(CONTENT_NOT_FOUND_PREDICATE, throwable -> { + LOGGER.info( + "Error happened when loading mime message associated with id {} of user {} in the vault", + deletedMessage.getMessageId().serialize(), + deletedMessage.getOwner().asString(), + throwable); + return Mono.empty(); + }) .map(messageContentStream -> AppendCommand.builder() .build(messageContentStream)); } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
