JAMES-2287 Cassandra blobId should implement the blobId interface & move in the blob-cassandra project
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/68e099ce Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/68e099ce Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/68e099ce Branch: refs/heads/master Commit: 68e099ce99243c8b58f82267910eb7a856d9014a Parents: 1815212 Author: Antoine Duprat <[email protected]> Authored: Tue Jan 16 13:59:35 2018 +0100 Committer: benwa <[email protected]> Committed: Fri Jan 19 09:07:27 2018 +0700 ---------------------------------------------------------------------- mailbox/cassandra/pom.xml | 4 + .../james/mailbox/cassandra/ids/BlobId.java | 73 --------------- .../mail/CassandraAttachmentDAOV2.java | 14 +-- .../cassandra/mail/CassandraBlobsDAO.java | 30 +++--- .../cassandra/mail/CassandraMessageDAO.java | 12 +-- .../james/mailbox/cassandra/ids/BlobIdTest.java | 93 ------------------- .../mail/CassandraAttachmentDAOV2Test.java | 4 +- .../mail/CassandraAttachmentFallbackTest.java | 8 +- .../cassandra/mail/CassandraBlobsDAOTest.java | 18 ++-- .../migration/AttachmentV2MigrationTest.java | 20 ++-- pom.xml | 5 + server/blob/blob-cassandra/pom.xml | 74 +++++++++++++++ .../james/blob/cassandra/CassandraBlobId.java | 76 +++++++++++++++ .../blob/cassandra/CassandraBlobIdTest.java | 86 +++++++++++++++++ .../src/test/resources/shattered-1.pdf | Bin 0 -> 422435 bytes .../src/test/resources/shattered-2.pdf | Bin 0 -> 422435 bytes server/blob/pom.xml | 1 + 17 files changed, 299 insertions(+), 219 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/pom.xml ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/pom.xml b/mailbox/cassandra/pom.xml index 5bffe2b..4377f83 100644 --- a/mailbox/cassandra/pom.xml +++ b/mailbox/cassandra/pom.xml @@ -64,6 +64,10 @@ </dependency> <dependency> <groupId>${project.groupId}</groupId> + <artifactId>blob-cassandra</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> <artifactId>james-server-task</artifactId> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/BlobId.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/BlobId.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/BlobId.java deleted file mode 100644 index 3824003..0000000 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/BlobId.java +++ /dev/null @@ -1,73 +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.mailbox.cassandra.ids; - -import org.apache.commons.codec.digest.DigestUtils; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; - -public class BlobId { - public static BlobId forPayload(byte[] payload) { - Preconditions.checkArgument(payload != null); - return new BlobId(DigestUtils.sha256Hex(payload)); - } - - public static BlobId from(String id) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(id)); - return new BlobId(id); - } - - private final String id; - - @VisibleForTesting - BlobId(String id) { - this.id = id; - } - - public String getId() { - return id; - } - - @Override - public final boolean equals(Object obj) { - if (obj instanceof BlobId) { - BlobId other = (BlobId) obj; - return Objects.equal(id, other.id); - } - return false; - } - - @Override - public final int hashCode() { - return Objects.hashCode(id); - } - - @Override - public String toString() { - return MoreObjects - .toStringHelper(this) - .add("id", id) - .toString(); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java index 34a654c..5a9727a 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java @@ -38,7 +38,7 @@ import java.util.concurrent.CompletableFuture; import javax.inject.Inject; import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; -import org.apache.james.mailbox.cassandra.ids.BlobId; +import org.apache.james.blob.cassandra.CassandraBlobId; import org.apache.james.mailbox.model.Attachment; import org.apache.james.mailbox.model.AttachmentId; @@ -50,11 +50,11 @@ import com.google.common.base.Preconditions; public class CassandraAttachmentDAOV2 { public static class DAOAttachment { private final AttachmentId attachmentId; - private final BlobId blobId; + private final CassandraBlobId blobId; private final String type; private final long size; - private DAOAttachment(AttachmentId attachmentId, BlobId blobId, String type, long size) { + private DAOAttachment(AttachmentId attachmentId, CassandraBlobId blobId, String type, long size) { this.attachmentId = attachmentId; this.blobId = blobId; this.type = type; @@ -65,7 +65,7 @@ public class CassandraAttachmentDAOV2 { return attachmentId; } - public BlobId getBlobId() { + public CassandraBlobId getBlobId() { return blobId; } @@ -104,7 +104,7 @@ public class CassandraAttachmentDAOV2 { } } - public static DAOAttachment from(Attachment attachment, BlobId blobId) { + public static DAOAttachment from(Attachment attachment, CassandraBlobId blobId) { return new DAOAttachment( attachment.getAttachmentId(), blobId, @@ -115,7 +115,7 @@ public class CassandraAttachmentDAOV2 { private static DAOAttachment fromRow(Row row) { return new DAOAttachment( AttachmentId.from(row.getString(ID)), - BlobId.from(row.getString(BLOB_ID)), + CassandraBlobId.from(row.getString(BLOB_ID)), row.getString(TYPE), row.getLong(SIZE)); } @@ -163,7 +163,7 @@ public class CassandraAttachmentDAOV2 { .setString(ID, attachment.getAttachmentId().getId()) .setLong(SIZE, attachment.getSize()) .setString(TYPE, attachment.getType()) - .setString(BLOB_ID, attachment.getBlobId().getId())); + .setString(BLOB_ID, attachment.getBlobId().asString())); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java index c7eabf7..5fc09aa 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java @@ -35,7 +35,7 @@ import javax.inject.Inject; import org.apache.commons.lang3.tuple.Pair; import org.apache.james.backends.cassandra.init.CassandraConfiguration; import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; -import org.apache.james.mailbox.cassandra.ids.BlobId; +import org.apache.james.blob.cassandra.CassandraBlobId; import org.apache.james.mailbox.cassandra.mail.utils.DataChunker; import org.apache.james.mailbox.cassandra.table.BlobTable; import org.apache.james.mailbox.cassandra.table.BlobTable.BlobParts; @@ -106,16 +106,16 @@ public class CassandraBlobsDAO { .value(BlobParts.DATA, bindMarker(BlobParts.DATA))); } - public CompletableFuture<BlobId> save(byte[] data) { + public CompletableFuture<CassandraBlobId> save(byte[] data) { Preconditions.checkNotNull(data); - BlobId blobId = BlobId.forPayload(data); + CassandraBlobId blobId = CassandraBlobId.forPayload(data); return saveBlobParts(data, blobId) .thenCompose(numberOfChunk -> saveBlobPartsReferences(blobId, numberOfChunk)) .thenApply(any -> blobId); } - private CompletableFuture<Integer> saveBlobParts(byte[] data, BlobId blobId) { + private CompletableFuture<Integer> saveBlobParts(byte[] data, CassandraBlobId blobId) { return FluentFutureStream.of( dataChunker.chunk(data, configuration.getBlobPartSize()) .map(pair -> writePart(pair.getRight(), blobId, pair.getKey()) @@ -131,29 +131,29 @@ public class CassandraBlobsDAO { return stream.reduce((first, second) -> second); } - private CompletableFuture<Void> writePart(ByteBuffer data, BlobId blobId, int position) { + private CompletableFuture<Void> writePart(ByteBuffer data, CassandraBlobId blobId, int position) { return cassandraAsyncExecutor.executeVoid( insertPart.bind() - .setString(BlobTable.ID, blobId.getId()) + .setString(BlobTable.ID, blobId.asString()) .setInt(BlobParts.CHUNK_NUMBER, position) .setBytes(BlobParts.DATA, data)); } - private CompletableFuture<Void> saveBlobPartsReferences(BlobId blobId, int numberOfChunk) { + private CompletableFuture<Void> saveBlobPartsReferences(CassandraBlobId blobId, int numberOfChunk) { return cassandraAsyncExecutor.executeVoid(insert.bind() - .setString(BlobTable.ID, blobId.getId()) + .setString(BlobTable.ID, blobId.asString()) .setInt(BlobTable.NUMBER_OF_CHUNK, numberOfChunk)); } - public CompletableFuture<byte[]> read(BlobId blobId) { + public CompletableFuture<byte[]> read(CassandraBlobId blobId) { return cassandraAsyncExecutor.executeSingleRow( select.bind() - .setString(BlobTable.ID, blobId.getId())) + .setString(BlobTable.ID, blobId.asString())) .thenCompose(row -> toDataParts(row, blobId)) .thenApply(this::concatenateDataParts); } - private CompletableFuture<Stream<BlobPart>> toDataParts(Optional<Row> blobRowOptional, BlobId blobId) { + private CompletableFuture<Stream<BlobPart>> toDataParts(Optional<Row> blobRowOptional, CassandraBlobId blobId) { return blobRowOptional.map(blobRow -> { int numOfChunk = blobRow.getInt(BlobTable.NUMBER_OF_CHUNK); return FluentFutureStream.of( @@ -184,20 +184,20 @@ public class CassandraBlobsDAO { return data; } - private CompletableFuture<BlobPart> readPart(BlobId blobId, int position) { + private CompletableFuture<BlobPart> readPart(CassandraBlobId blobId, int position) { return cassandraAsyncExecutor.executeSingleRow( selectPart.bind() - .setString(BlobTable.ID, blobId.getId()) + .setString(BlobTable.ID, blobId.asString()) .setInt(BlobParts.CHUNK_NUMBER, position)) .thenApply(row -> new BlobPart(blobId, position, row)); } private static class BlobPart { - private final BlobId blobId; + private final CassandraBlobId blobId; private final int position; private final Optional<Row> row; - public BlobPart(BlobId blobId, int position, Optional<Row> row) { + public BlobPart(CassandraBlobId blobId, int position, Optional<Row> row) { Preconditions.checkNotNull(blobId); Preconditions.checkArgument(position >= 0, "position need to be positive"); this.blobId = blobId; http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java index 83477fb..c80f982 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java @@ -59,7 +59,7 @@ import org.apache.james.backends.cassandra.init.CassandraConfiguration; import org.apache.james.backends.cassandra.init.CassandraTypesProvider; import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; import org.apache.james.backends.cassandra.utils.CassandraUtils; -import org.apache.james.mailbox.cassandra.ids.BlobId; +import org.apache.james.blob.cassandra.CassandraBlobId; import org.apache.james.mailbox.cassandra.ids.CassandraMessageId; import org.apache.james.mailbox.cassandra.mail.utils.Limit; import org.apache.james.mailbox.cassandra.table.CassandraMessageV2Table; @@ -176,7 +176,7 @@ public class CassandraMessageDAO { cassandraAsyncExecutor.executeVoid(boundWriteStatement(message, pair))); } - private CompletableFuture<Pair<BlobId, BlobId>> saveContent(MailboxMessage message) throws MailboxException { + private CompletableFuture<Pair<CassandraBlobId, CassandraBlobId>> saveContent(MailboxMessage message) throws MailboxException { try { return CompletableFutureUtil.combine( blobsDAO.save( @@ -191,7 +191,7 @@ public class CassandraMessageDAO { } } - private BoundStatement boundWriteStatement(MailboxMessage message, Pair<BlobId, BlobId> pair) { + private BoundStatement boundWriteStatement(MailboxMessage message, Pair<CassandraBlobId, CassandraBlobId> pair) { CassandraMessageId messageId = (CassandraMessageId) message.getMessageId(); return insert.bind() .setUUID(MESSAGE_ID, messageId.get()) @@ -199,8 +199,8 @@ public class CassandraMessageDAO { .setInt(BODY_START_OCTET, (int) (message.getHeaderOctets())) .setLong(FULL_CONTENT_OCTETS, message.getFullContentOctets()) .setLong(BODY_OCTECTS, message.getBodyOctets()) - .setString(BODY_CONTENT, pair.getRight().getId()) - .setString(HEADER_CONTENT, pair.getLeft().getId()) + .setString(BODY_CONTENT, pair.getRight().asString()) + .setString(HEADER_CONTENT, pair.getLeft().asString()) .setLong(TEXTUAL_LINE_COUNT, Optional.ofNullable(message.getTextualLineCount()).orElse(DEFAULT_LONG_VALUE)) .setList(PROPERTIES, buildPropertiesUdt(message)) .setList(ATTACHMENTS, buildAttachmentUdt(message)); @@ -370,7 +370,7 @@ public class CassandraMessageDAO { } private CompletableFuture<byte[]> getFieldContent(String field, Row row) { - return blobsDAO.read(BlobId.from(row.getString(field))); + return blobsDAO.read(CassandraBlobId.from(row.getString(field))); } public static MessageResult notFound(ComposedMessageIdWithMetaData id) { http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/ids/BlobIdTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/ids/BlobIdTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/ids/BlobIdTest.java deleted file mode 100644 index e8e3f13..0000000 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/ids/BlobIdTest.java +++ /dev/null @@ -1,93 +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.mailbox.cassandra.ids; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.charset.StandardCharsets; - -import org.apache.james.util.ClassLoaderUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import nl.jqno.equalsverifier.EqualsVerifier; - -public class BlobIdTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void shouldRespectBeanContract() { - EqualsVerifier.forClass(BlobId.class).verify(); - } - - @Test - public void fromShouldConstructBlobId() { - String id = "111"; - assertThat(BlobId.from(id)) - .isEqualTo(new BlobId(id)); - } - - @Test - public void fromShouldThrowOnNull() { - expectedException.expect(IllegalArgumentException.class); - - BlobId.from(null); - } - - @Test - public void fromShouldThrowOnEmpty() { - expectedException.expect(IllegalArgumentException.class); - - BlobId.from(""); - } - - @Test - public void forPayloadShouldThrowOnNull() { - expectedException.expect(IllegalArgumentException.class); - - BlobId.forPayload(null); - } - - @Test - public void forPayloadShouldHashEmptyArray() { - BlobId blobId = BlobId.forPayload(new byte[0]); - - assertThat(blobId.getId()).isEqualTo("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - } - - @Test - public void forPayloadShouldHashArray() { - BlobId blobId = BlobId.forPayload("content".getBytes(StandardCharsets.UTF_8)); - - assertThat(blobId.getId()).isEqualTo("ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73"); - } - - @Test - public void forPayloadShouldCalculateDifferentHashesWhenCraftedSha1Collision() throws Exception { - byte[] payload1 = ClassLoaderUtils.getSystemResourceAsByteArray("shattered-1.pdf"); - byte[] payload2 = ClassLoaderUtils.getSystemResourceAsByteArray("shattered-2.pdf"); - BlobId blobId1 = BlobId.forPayload(payload1); - BlobId blobId2 = BlobId.forPayload(payload2); - assertThat(blobId1).isNotEqualTo(blobId2); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java index 84e19bc..f5a8285 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java @@ -26,7 +26,7 @@ import java.util.Optional; import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.backends.cassandra.DockerCassandraRule; -import org.apache.james.mailbox.cassandra.ids.BlobId; +import org.apache.james.blob.cassandra.CassandraBlobId; import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAOV2.DAOAttachment; import org.apache.james.mailbox.cassandra.modules.CassandraAttachmentModule; import org.apache.james.mailbox.model.Attachment; @@ -75,7 +75,7 @@ public class CassandraAttachmentDAOV2Test { .type("application/json") .bytes("{\"property\":`\"value\"}".getBytes(StandardCharsets.UTF_8)) .build(); - BlobId blobId = BlobId.from("blobId"); + CassandraBlobId blobId = CassandraBlobId.from("blobId"); DAOAttachment daoAttachment = CassandraAttachmentDAOV2.from(attachment, blobId); testee.storeAttachment(daoAttachment).join(); http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTest.java index 6ac6409..4c42a2d 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTest.java @@ -29,7 +29,7 @@ import org.apache.james.backends.cassandra.DockerCassandraRule; import org.apache.james.backends.cassandra.init.CassandraConfiguration; import org.apache.james.backends.cassandra.init.CassandraModuleComposite; import org.apache.james.backends.cassandra.utils.CassandraUtils; -import org.apache.james.mailbox.cassandra.ids.BlobId; +import org.apache.james.blob.cassandra.CassandraBlobId; import org.apache.james.mailbox.cassandra.ids.CassandraMessageId; import org.apache.james.mailbox.cassandra.modules.CassandraAttachmentModule; import org.apache.james.mailbox.cassandra.modules.CassandraBlobModule; @@ -108,7 +108,7 @@ public class CassandraAttachmentFallbackTest { .bytes("{\"property\":`\"different\"}".getBytes(StandardCharsets.UTF_8)) .build(); - BlobId blobId = blobsDAO.save(attachment.getBytes()).join(); + CassandraBlobId blobId = blobsDAO.save(attachment.getBytes()).join(); attachmentDAOV2.storeAttachment(CassandraAttachmentDAOV2.from(attachment, blobId)).join(); attachmentDAO.storeAttachment(otherAttachment).join(); @@ -143,7 +143,7 @@ public class CassandraAttachmentFallbackTest { .bytes("{\"property\":`\"different\"}".getBytes(StandardCharsets.UTF_8)) .build(); - BlobId blobId = blobsDAO.save(attachment.getBytes()).join(); + CassandraBlobId blobId = blobsDAO.save(attachment.getBytes()).join(); attachmentDAOV2.storeAttachment(CassandraAttachmentDAOV2.from(attachment, blobId)).join(); attachmentDAO.storeAttachment(otherAttachment).join(); @@ -178,7 +178,7 @@ public class CassandraAttachmentFallbackTest { .bytes("{\"property\":`\"different\"}".getBytes(StandardCharsets.UTF_8)) .build(); - BlobId blobId = blobsDAO.save(attachment.getBytes()).join(); + CassandraBlobId blobId = blobsDAO.save(attachment.getBytes()).join(); attachmentDAOV2.storeAttachment(CassandraAttachmentDAOV2.from(attachment, blobId)).join(); attachmentDAO.storeAttachment(otherAttachment).join(); http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAOTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAOTest.java index 9709bb4..e92316a 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAOTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAOTest.java @@ -28,7 +28,7 @@ import java.nio.charset.StandardCharsets; import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.backends.cassandra.DockerCassandraRule; import org.apache.james.backends.cassandra.init.CassandraConfiguration; -import org.apache.james.mailbox.cassandra.ids.BlobId; +import org.apache.james.blob.cassandra.CassandraBlobId; import org.apache.james.mailbox.cassandra.modules.CassandraBlobModule; import org.junit.After; import org.junit.Before; @@ -70,7 +70,7 @@ public class CassandraBlobsDAOTest { @Test public void saveShouldSaveEmptyData() throws Exception { - BlobId blobId = testee.save(new byte[]{}).join(); + CassandraBlobId blobId = testee.save(new byte[]{}).join(); byte[] bytes = testee.read(blobId).join(); @@ -79,7 +79,7 @@ public class CassandraBlobsDAOTest { @Test public void saveShouldSaveBlankData() throws Exception { - BlobId blobId = testee.save("".getBytes(StandardCharsets.UTF_8)).join(); + CassandraBlobId blobId = testee.save("".getBytes(StandardCharsets.UTF_8)).join(); byte[] bytes = testee.read(blobId).join(); @@ -88,21 +88,21 @@ public class CassandraBlobsDAOTest { @Test public void saveShouldReturnBlobId() throws Exception { - BlobId blobId = testee.save("toto".getBytes(StandardCharsets.UTF_8)).join(); + CassandraBlobId blobId = testee.save("toto".getBytes(StandardCharsets.UTF_8)).join(); - assertThat(blobId).isEqualTo(BlobId.from("31f7a65e315586ac198bd798b6629ce4903d0899476d5741a9f32e2e521b6a66")); + assertThat(blobId).isEqualTo(CassandraBlobId.from("31f7a65e315586ac198bd798b6629ce4903d0899476d5741a9f32e2e521b6a66")); } @Test public void readShouldBeEmptyWhenNoExisting() throws IOException { - byte[] bytes = testee.read(BlobId.from("unknown")).join(); + byte[] bytes = testee.read(CassandraBlobId.from("unknown")).join(); assertThat(bytes).isEmpty(); } @Test public void readShouldReturnSavedData() throws IOException { - BlobId blobId = testee.save("toto".getBytes(StandardCharsets.UTF_8)).join(); + CassandraBlobId blobId = testee.save("toto".getBytes(StandardCharsets.UTF_8)).join(); byte[] bytes = testee.read(blobId).join(); @@ -112,7 +112,7 @@ public class CassandraBlobsDAOTest { @Test public void readShouldReturnLongSavedData() throws IOException { String longString = Strings.repeat("0123456789\n", 1000); - BlobId blobId = testee.save(longString.getBytes(StandardCharsets.UTF_8)).join(); + CassandraBlobId blobId = testee.save(longString.getBytes(StandardCharsets.UTF_8)).join(); byte[] bytes = testee.read(blobId).join(); @@ -122,7 +122,7 @@ public class CassandraBlobsDAOTest { @Test public void readShouldReturnSplitSavedDataByChunk() throws IOException { String longString = Strings.repeat("0123456789\n", MULTIPLE_CHUNK_SIZE); - BlobId blobId = testee.save(longString.getBytes(StandardCharsets.UTF_8)).join(); + CassandraBlobId blobId = testee.save(longString.getBytes(StandardCharsets.UTF_8)).join(); byte[] bytes = testee.read(blobId).join(); http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/AttachmentV2MigrationTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/AttachmentV2MigrationTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/AttachmentV2MigrationTest.java index 0f456ac..b53205e 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/AttachmentV2MigrationTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/AttachmentV2MigrationTest.java @@ -34,7 +34,7 @@ import org.apache.james.backends.cassandra.init.CassandraConfiguration; import org.apache.james.backends.cassandra.init.CassandraModuleComposite; import org.apache.james.backends.cassandra.migration.Migration; import org.apache.james.backends.cassandra.utils.CassandraUtils; -import org.apache.james.mailbox.cassandra.ids.BlobId; +import org.apache.james.blob.cassandra.CassandraBlobId; import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAO; import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAOV2; import org.apache.james.mailbox.cassandra.mail.CassandraBlobsDAO; @@ -112,12 +112,12 @@ public class AttachmentV2MigrationTest { migration.run(); assertThat(attachmentDAOV2.getAttachment(ATTACHMENT_ID).join()) - .contains(CassandraAttachmentDAOV2.from(attachment1, BlobId.forPayload(attachment1.getBytes()))); + .contains(CassandraAttachmentDAOV2.from(attachment1, CassandraBlobId.forPayload(attachment1.getBytes()))); assertThat(attachmentDAOV2.getAttachment(ATTACHMENT_ID_2).join()) - .contains(CassandraAttachmentDAOV2.from(attachment2, BlobId.forPayload(attachment2.getBytes()))); - assertThat(blobsDAO.read(BlobId.forPayload(attachment1.getBytes())).join()) + .contains(CassandraAttachmentDAOV2.from(attachment2, CassandraBlobId.forPayload(attachment2.getBytes()))); + assertThat(blobsDAO.read(CassandraBlobId.forPayload(attachment1.getBytes())).join()) .isEqualTo(attachment1.getBytes()); - assertThat(blobsDAO.read(BlobId.forPayload(attachment2.getBytes())).join()) + assertThat(blobsDAO.read(CassandraBlobId.forPayload(attachment2.getBytes())).join()) .isEqualTo(attachment2.getBytes()); } @@ -172,9 +172,9 @@ public class AttachmentV2MigrationTest { attachment1, attachment2)); when(blobsDAO.save(attachment1.getBytes())) - .thenReturn(CompletableFuture.completedFuture(BlobId.forPayload(attachment1.getBytes()))); + .thenReturn(CompletableFuture.completedFuture(CassandraBlobId.forPayload(attachment1.getBytes()))); when(blobsDAO.save(attachment2.getBytes())) - .thenReturn(CompletableFuture.completedFuture(BlobId.forPayload(attachment2.getBytes()))); + .thenReturn(CompletableFuture.completedFuture(CassandraBlobId.forPayload(attachment2.getBytes()))); when(attachmentDAOV2.storeAttachment(any())).thenThrow(new RuntimeException()); assertThat(migration.run()).isEqualTo(Migration.Result.PARTIAL); @@ -191,9 +191,9 @@ public class AttachmentV2MigrationTest { attachment1, attachment2)); when(blobsDAO.save(attachment1.getBytes())) - .thenReturn(CompletableFuture.completedFuture(BlobId.forPayload(attachment1.getBytes()))); + .thenReturn(CompletableFuture.completedFuture(CassandraBlobId.forPayload(attachment1.getBytes()))); when(blobsDAO.save(attachment2.getBytes())) - .thenReturn(CompletableFuture.completedFuture(BlobId.forPayload(attachment2.getBytes()))); + .thenReturn(CompletableFuture.completedFuture(CassandraBlobId.forPayload(attachment2.getBytes()))); when(attachmentDAOV2.storeAttachment(any())).thenReturn(CompletableFuture.completedFuture(null)); when(attachmentDAO.deleteAttachment(any())).thenThrow(new RuntimeException()); @@ -211,7 +211,7 @@ public class AttachmentV2MigrationTest { attachment1, attachment2)); when(blobsDAO.save(attachment1.getBytes())) - .thenReturn(CompletableFuture.completedFuture(BlobId.forPayload(attachment1.getBytes()))); + .thenReturn(CompletableFuture.completedFuture(CassandraBlobId.forPayload(attachment1.getBytes()))); when(blobsDAO.save(attachment2.getBytes())) .thenThrow(new RuntimeException()); when(attachmentDAOV2.storeAttachment(any())).thenReturn(CompletableFuture.completedFuture(null)); http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 420ffaa..edf90d3 100644 --- a/pom.xml +++ b/pom.xml @@ -975,6 +975,11 @@ </dependency> <dependency> <groupId>${project.groupId}</groupId> + <artifactId>blob-cassandra</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> <artifactId>james-server-cassandra-guice</artifactId> <version>${project.version}</version> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/server/blob/blob-cassandra/pom.xml ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/pom.xml b/server/blob/blob-cassandra/pom.xml new file mode 100644 index 0000000..387d222 --- /dev/null +++ b/server/blob/blob-cassandra/pom.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>james-server-blob</artifactId> + <groupId>org.apache.james</groupId> + <version>3.1.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>blob-cassandra</artifactId> + <packaging>jar</packaging> + + <name>Apache James :: Server :: Blob :: Cassandra</name> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>blob-api</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>james-server-util-java8</artifactId> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + </dependency> + <dependency> + <groupId>nl.jqno.equalsverifier</groupId> + <artifactId>equalsverifier</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-launcher</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobId.java ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobId.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobId.java new file mode 100644 index 0000000..5dba57e --- /dev/null +++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobId.java @@ -0,0 +1,76 @@ +/**************************************************************** + * 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.cassandra; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.james.blob.api.BlobId; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +public class CassandraBlobId implements BlobId { + + public static CassandraBlobId forPayload(byte[] payload) { + Preconditions.checkArgument(payload != null); + return new CassandraBlobId(DigestUtils.sha256Hex(payload)); + } + + public static CassandraBlobId from(String id) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(id)); + return new CassandraBlobId(id); + } + + private final String id; + + @VisibleForTesting + CassandraBlobId(String id) { + this.id = id; + } + + @Override + public String asString() { + return id; + } + + @Override + public final boolean equals(Object obj) { + if (obj instanceof CassandraBlobId) { + CassandraBlobId other = (CassandraBlobId) obj; + return Objects.equal(id, other.id); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hashCode(id); + } + + @Override + public String toString() { + return MoreObjects + .toStringHelper(this) + .add("id", id) + .toString(); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobIdTest.java ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobIdTest.java b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobIdTest.java new file mode 100644 index 0000000..2fe0db2 --- /dev/null +++ b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobIdTest.java @@ -0,0 +1,86 @@ +/**************************************************************** + * 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.cassandra; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.nio.charset.StandardCharsets; + +import org.apache.james.util.ClassLoaderUtils; +import org.junit.jupiter.api.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; + +public class CassandraBlobIdTest { + + @Test + public void shouldRespectBeanContract() { + EqualsVerifier.forClass(CassandraBlobId.class).verify(); + } + + @Test + public void fromShouldConstructBlobId() { + String id = "111"; + assertThat(CassandraBlobId.from(id)) + .isEqualTo(new CassandraBlobId(id)); + } + + @Test + public void fromShouldThrowOnNull() { + assertThatThrownBy(() -> CassandraBlobId.from(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void fromShouldThrowOnEmpty() { + assertThatThrownBy(() -> CassandraBlobId.from("")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void forPayloadShouldThrowOnNull() { + assertThatThrownBy(() -> CassandraBlobId.forPayload(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void forPayloadShouldHashEmptyArray() { + CassandraBlobId blobId = CassandraBlobId.forPayload(new byte[0]); + + assertThat(blobId.asString()).isEqualTo("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + } + + @Test + public void forPayloadShouldHashArray() { + CassandraBlobId blobId = CassandraBlobId.forPayload("content".getBytes(StandardCharsets.UTF_8)); + + assertThat(blobId.asString()).isEqualTo("ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73"); + } + + @Test + public void forPayloadShouldCalculateDifferentHashesWhenCraftedSha1Collision() throws Exception { + byte[] payload1 = ClassLoaderUtils.getSystemResourceAsByteArray("shattered-1.pdf"); + byte[] payload2 = ClassLoaderUtils.getSystemResourceAsByteArray("shattered-2.pdf"); + CassandraBlobId blobId1 = CassandraBlobId.forPayload(payload1); + CassandraBlobId blobId2 = CassandraBlobId.forPayload(payload2); + assertThat(blobId1).isNotEqualTo(blobId2); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/server/blob/blob-cassandra/src/test/resources/shattered-1.pdf ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/test/resources/shattered-1.pdf b/server/blob/blob-cassandra/src/test/resources/shattered-1.pdf new file mode 100644 index 0000000..ba9aaa1 Binary files /dev/null and b/server/blob/blob-cassandra/src/test/resources/shattered-1.pdf differ http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/server/blob/blob-cassandra/src/test/resources/shattered-2.pdf ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/test/resources/shattered-2.pdf b/server/blob/blob-cassandra/src/test/resources/shattered-2.pdf new file mode 100644 index 0000000..b621eec Binary files /dev/null and b/server/blob/blob-cassandra/src/test/resources/shattered-2.pdf differ http://git-wip-us.apache.org/repos/asf/james-project/blob/68e099ce/server/blob/pom.xml ---------------------------------------------------------------------- diff --git a/server/blob/pom.xml b/server/blob/pom.xml index d86a2bd..21cfffb 100644 --- a/server/blob/pom.xml +++ b/server/blob/pom.xml @@ -34,6 +34,7 @@ <modules> <module>blob-api</module> + <module>blob-cassandra</module> </modules> </project> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
