http://git-wip-us.apache.org/repos/asf/james-project/blob/4e8bcc66/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobModule.java ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobModule.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobModule.java new file mode 100644 index 0000000..234988a --- /dev/null +++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobModule.java @@ -0,0 +1,68 @@ +/**************************************************************** + * 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 java.util.List; + +import org.apache.james.backends.cassandra.components.CassandraModule; +import org.apache.james.backends.cassandra.components.CassandraTable; +import org.apache.james.backends.cassandra.components.CassandraType; + +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.schemabuilder.SchemaBuilder; +import com.google.common.collect.ImmutableList; + +public class CassandraBlobModule implements CassandraModule { + + private final List<CassandraTable> tables; + private final List<CassandraType> types; + + public CassandraBlobModule() { + tables = ImmutableList.of( + new CassandraTable(BlobTable.TABLE_NAME, + SchemaBuilder.createTable(BlobTable.TABLE_NAME) + .ifNotExists() + .addPartitionKey(BlobTable.ID, DataType.text()) + .addClusteringColumn(BlobTable.NUMBER_OF_CHUNK, DataType.cint()) + .withOptions() + .comment("Holds information for retrieving all blob parts composing this blob. " + + "Messages` headers and bodies are stored as blobparts.")), + new CassandraTable(BlobTable.BlobParts.TABLE_NAME, + SchemaBuilder.createTable(BlobTable.BlobParts.TABLE_NAME) + .ifNotExists() + .addPartitionKey(BlobTable.ID, DataType.text()) + .addClusteringColumn(BlobTable.BlobParts.CHUNK_NUMBER, DataType.cint()) + .addColumn(BlobTable.BlobParts.DATA, DataType.blob()) + .withOptions() + .comment("Holds blob parts composing blobs ." + + "Messages` headers and bodies are stored, chunked in blobparts."))); + types = ImmutableList.of(); + } + + @Override + public List<CassandraTable> moduleTables() { + return tables; + } + + @Override + public List<CassandraType> moduleTypes() { + return types; + } +}
http://git-wip-us.apache.org/repos/asf/james-project/blob/4e8bcc66/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobsDAO.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..97965ec --- /dev/null +++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobsDAO.java @@ -0,0 +1,210 @@ +/**************************************************************** + * 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 com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto; +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; + +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +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.blob.api.BlobId; +import org.apache.james.blob.api.ObjectStore; +import org.apache.james.blob.cassandra.BlobTable.BlobParts; +import org.apache.james.blob.cassandra.utils.DataChunker; +import org.apache.james.util.FluentFutureStream; +import org.apache.james.util.OptionalUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Bytes; + +public class CassandraBlobsDAO implements ObjectStore { + private static final Logger LOGGER = LoggerFactory.getLogger(CassandraBlobsDAO.class); + private final CassandraAsyncExecutor cassandraAsyncExecutor; + private final PreparedStatement insert; + private final PreparedStatement insertPart; + private final PreparedStatement select; + private final PreparedStatement selectPart; + private final DataChunker dataChunker; + private final CassandraConfiguration configuration; + + @Inject + public CassandraBlobsDAO(Session session, CassandraConfiguration cassandraConfiguration) { + this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session); + this.configuration = cassandraConfiguration; + this.dataChunker = new DataChunker(); + this.insert = prepareInsert(session); + this.select = prepareSelect(session); + + this.insertPart = prepareInsertPart(session); + this.selectPart = prepareSelectPart(session); + } + + @VisibleForTesting + public CassandraBlobsDAO(Session session) { + this(session, CassandraConfiguration.DEFAULT_CONFIGURATION); + } + + private PreparedStatement prepareSelect(Session session) { + return session.prepare(select() + .from(BlobTable.TABLE_NAME) + .where(eq(BlobTable.ID, bindMarker(BlobTable.ID)))); + } + + private PreparedStatement prepareSelectPart(Session session) { + return session.prepare(select() + .from(BlobParts.TABLE_NAME) + .where(eq(BlobTable.ID, bindMarker(BlobTable.ID))) + .and(eq(BlobParts.CHUNK_NUMBER, bindMarker(BlobParts.CHUNK_NUMBER)))); + } + + private PreparedStatement prepareInsert(Session session) { + return session.prepare(insertInto(BlobTable.TABLE_NAME) + .value(BlobTable.ID, bindMarker(BlobTable.ID)) + .value(BlobTable.NUMBER_OF_CHUNK, bindMarker(BlobTable.NUMBER_OF_CHUNK))); + } + + private PreparedStatement prepareInsertPart(Session session) { + return session.prepare(insertInto(BlobParts.TABLE_NAME) + .value(BlobTable.ID, bindMarker(BlobTable.ID)) + .value(BlobParts.CHUNK_NUMBER, bindMarker(BlobParts.CHUNK_NUMBER)) + .value(BlobParts.DATA, bindMarker(BlobParts.DATA))); + } + + @Override + public CompletableFuture<BlobId> save(byte[] data) { + Preconditions.checkNotNull(data); + + CassandraBlobId blobId = CassandraBlobId.forPayload(data); + return saveBlobParts(data, blobId) + .thenCompose(numberOfChunk -> saveBlobPartsReferences(blobId, numberOfChunk)) + .thenApply(any -> 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()) + .thenApply(partId -> Pair.of(pair.getKey(), partId)))) + .completableFuture() + .thenApply(stream -> + getLastOfStream(stream) + .map(numOfChunkAndPartId -> numOfChunkAndPartId.getLeft() + 1) + .orElse(0)); + } + + private static <T> Optional<T> getLastOfStream(Stream<T> stream) { + return stream.reduce((first, second) -> second); + } + + private CompletableFuture<Void> writePart(ByteBuffer data, CassandraBlobId blobId, int position) { + return cassandraAsyncExecutor.executeVoid( + insertPart.bind() + .setString(BlobTable.ID, blobId.asString()) + .setInt(BlobParts.CHUNK_NUMBER, position) + .setBytes(BlobParts.DATA, data)); + } + + private CompletableFuture<Void> saveBlobPartsReferences(CassandraBlobId blobId, int numberOfChunk) { + return cassandraAsyncExecutor.executeVoid(insert.bind() + .setString(BlobTable.ID, blobId.asString()) + .setInt(BlobTable.NUMBER_OF_CHUNK, numberOfChunk)); + } + + @Override + public CompletableFuture<byte[]> read(BlobId blobId) { + return cassandraAsyncExecutor.executeSingleRow( + select.bind() + .setString(BlobTable.ID, blobId.asString())) + .thenCompose(row -> toDataParts(row, blobId)) + .thenApply(this::concatenateDataParts); + } + + private CompletableFuture<Stream<BlobPart>> toDataParts(Optional<Row> blobRowOptional, BlobId blobId) { + return blobRowOptional.map(blobRow -> { + int numOfChunk = blobRow.getInt(BlobTable.NUMBER_OF_CHUNK); + return FluentFutureStream.of( + IntStream.range(0, numOfChunk) + .mapToObj(position -> readPart(blobId, position))) + .completableFuture(); + }).orElseGet(() -> { + LOGGER.warn("Could not retrieve blob metadata for {}", blobId); + return CompletableFuture.completedFuture(Stream.empty()); + }); + } + + private byte[] concatenateDataParts(Stream<BlobPart> blobParts) { + ImmutableList<byte[]> parts = blobParts + .map(blobPart -> OptionalUtils.executeIfEmpty( + blobPart.row, + () -> LOGGER.warn("Missing blob part for blobId {} and position {}", blobPart.blobId, blobPart.position))) + .flatMap(OptionalUtils::toStream) + .map(this::rowToData) + .collect(Guavate.toImmutableList()); + + return Bytes.concat(parts.toArray(new byte[parts.size()][])); + } + + private byte[] rowToData(Row row) { + byte[] data = new byte[row.getBytes(BlobParts.DATA).remaining()]; + row.getBytes(BlobParts.DATA).get(data); + return data; + } + + private CompletableFuture<BlobPart> readPart(BlobId blobId, int position) { + return cassandraAsyncExecutor.executeSingleRow( + selectPart.bind() + .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 int position; + private final Optional<Row> row; + + public BlobPart(BlobId blobId, int position, Optional<Row> row) { + Preconditions.checkNotNull(blobId); + Preconditions.checkArgument(position >= 0, "position need to be positive"); + this.blobId = blobId; + this.position = position; + this.row = row; + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4e8bcc66/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/utils/DataChunker.java ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/utils/DataChunker.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/utils/DataChunker.java new file mode 100644 index 0000000..aad71dc --- /dev/null +++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/utils/DataChunker.java @@ -0,0 +1,52 @@ +/**************************************************************** + * 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.utils; + +import java.nio.ByteBuffer; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.apache.commons.lang3.tuple.Pair; + +import com.google.common.base.Preconditions; + +public class DataChunker { + + public Stream<Pair<Integer, ByteBuffer>> chunk(byte[] data, int chunkSize) { + Preconditions.checkNotNull(data); + Preconditions.checkArgument(chunkSize > 0, "ChunkSize can not be negative"); + + int size = data.length; + int fullChunkCount = size / chunkSize; + + return Stream.concat( + IntStream.range(0, fullChunkCount) + .mapToObj(i -> Pair.of(i, ByteBuffer.wrap(data, i * chunkSize, chunkSize))), + lastChunk(data, chunkSize * fullChunkCount, fullChunkCount)); + } + + private Stream<Pair<Integer, ByteBuffer>> lastChunk(byte[] data, int offset, int index) { + if (offset == data.length && index > 0) { + return Stream.empty(); + } + return Stream.of(Pair.of(index, ByteBuffer.wrap(data, offset, data.length - offset))); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4e8bcc66/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobsDAOTest.java ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobsDAOTest.java b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobsDAOTest.java new file mode 100644 index 0000000..62e73cd --- /dev/null +++ b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobsDAOTest.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 java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.james.backends.cassandra.CassandraCluster; +import org.apache.james.backends.cassandra.DockerCassandraExtension; +import org.apache.james.backends.cassandra.DockerCassandraExtension.DockerCassandra; +import org.apache.james.backends.cassandra.init.CassandraConfiguration; +import org.apache.james.blob.api.BlobId; +import org.apache.james.blob.api.ObjectStore; +import org.apache.james.blob.api.ObjectStoreContract; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.google.common.base.Strings; + +@ExtendWith(DockerCassandraExtension.class) +public class CassandraBlobsDAOTest implements ObjectStoreContract { + + private static final int CHUNK_SIZE = 10240; + private static final int MULTIPLE_CHUNK_SIZE = 3; + + private CassandraCluster cassandra; + private CassandraBlobsDAO testee; + + @BeforeEach + public void setUp(DockerCassandra dockerCassandra) { + cassandra = CassandraCluster.create( + new CassandraBlobModule(), dockerCassandra.getIp(), dockerCassandra.getBindingPort()); + + testee = new CassandraBlobsDAO(cassandra.getConf(), + CassandraConfiguration.builder() + .blobPartSize(CHUNK_SIZE) + .build()); + } + + @AfterEach + public void tearDown() { + cassandra.close(); + } + + @Override + public ObjectStore testee() { + return testee; + } + + @Override + public BlobId from(String blodIdAsString) { + return CassandraBlobId.from(blodIdAsString); + } + + @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(); + + byte[] bytes = testee.read(blobId).join(); + + assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo(longString); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/4e8bcc66/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/utils/DataChunkerTest.java ---------------------------------------------------------------------- diff --git a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/utils/DataChunkerTest.java b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/utils/DataChunkerTest.java new file mode 100644 index 0000000..0f97a0d --- /dev/null +++ b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/utils/DataChunkerTest.java @@ -0,0 +1,110 @@ +/**************************************************************** + * 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.utils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Bytes; + +public class DataChunkerTest { + + public static final int CHUNK_SIZE = 10; + + private DataChunker testee; + + @BeforeEach + public void setUp() { + testee = new DataChunker(); + } + + @Test + public void chunkShouldThrowOnNullData() { + assertThatThrownBy(() -> testee.chunk(null, CHUNK_SIZE)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void chunkShouldThrowOnNegativeChunkSize() { + int chunkSize = -1; + assertThatThrownBy(() -> testee.chunk(new byte[0], chunkSize)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void chunkShouldThrowOnZeroChunkSize() { + int chunkSize = 0; + assertThatThrownBy(() -> testee.chunk(new byte[0], chunkSize)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void chunkShouldReturnOneEmptyArrayWhenInputEmpty() { + Stream<Pair<Integer, ByteBuffer>> chunks = testee.chunk(new byte[0], CHUNK_SIZE); + ByteBuffer emptyBuffer = ByteBuffer.wrap(new byte[0]); + assertThat(chunks) + .containsOnlyElementsOf(ImmutableList.of(Pair.of(0, emptyBuffer))); + } + + @Test + public void chunkShouldReturnOneArrayWhenInputLessThanChunkSize() { + byte[] data = "12345".getBytes(StandardCharsets.UTF_8); + + Stream<Pair<Integer, ByteBuffer>> chunks = testee.chunk(data, CHUNK_SIZE); + + assertThat(chunks) + .containsOnlyElementsOf(ImmutableList.of(Pair.of(0, ByteBuffer.wrap(data)))); + } + + @Test + public void chunkShouldReturnOneArrayWhenInputEqualsChunkSize() { + byte[] data = "1234567890".getBytes(StandardCharsets.UTF_8); + assertThat(data.length).isEqualTo(CHUNK_SIZE); + + Stream<Pair<Integer, ByteBuffer>> chunks = testee.chunk(data, CHUNK_SIZE); + + assertThat(chunks) + .containsOnlyElementsOf(ImmutableList.of(Pair.of(0, ByteBuffer.wrap(data)))); + } + + @Test + public void chunkShouldReturnSeveralArrayWhenInputBiggerThanChunkSize() { + byte[] part1 = "1234567890".getBytes(StandardCharsets.UTF_8); + byte[] part2 = "12345".getBytes(StandardCharsets.UTF_8); + byte[] data = Bytes.concat(part1, part2); + + Stream<Pair<Integer, ByteBuffer>> chunks = testee.chunk(data, CHUNK_SIZE); + + assertThat(chunks) + .containsOnlyElementsOf(ImmutableList.of( + Pair.of(0, ByteBuffer.wrap(part1)), + Pair.of(1, ByteBuffer.wrap(part2)))); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4e8bcc66/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java ---------------------------------------------------------------------- diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java index b233920..3d1c404 100644 --- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java +++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java @@ -23,6 +23,7 @@ import javax.inject.Singleton; import org.apache.james.adapter.mailbox.store.UserRepositoryAuthenticator; import org.apache.james.adapter.mailbox.store.UserRepositoryAuthorizator; import org.apache.james.backends.cassandra.components.CassandraModule; +import org.apache.james.blob.cassandra.CassandraBlobsDAO; import org.apache.james.mailbox.AttachmentManager; import org.apache.james.mailbox.BlobManager; import org.apache.james.mailbox.MailboxListener; @@ -43,7 +44,6 @@ import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAO; import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAOV2; import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentMessageIdDAO; import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentOwnerDAO; -import org.apache.james.mailbox.cassandra.mail.CassandraBlobsDAO; import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO; import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO; @@ -159,7 +159,7 @@ public class CassandraMailboxModule extends AbstractModule { cassandraDataDefinitions.addBinding().to(org.apache.james.mailbox.cassandra.modules.CassandraAnnotationModule.class); cassandraDataDefinitions.addBinding().to(org.apache.james.mailbox.cassandra.modules.CassandraApplicableFlagsModule.class); cassandraDataDefinitions.addBinding().to(org.apache.james.mailbox.cassandra.modules.CassandraAttachmentModule.class); - cassandraDataDefinitions.addBinding().to(org.apache.james.mailbox.cassandra.modules.CassandraBlobModule.class); + cassandraDataDefinitions.addBinding().to(org.apache.james.blob.cassandra.CassandraBlobModule.class); cassandraDataDefinitions.addBinding().to(org.apache.james.mailbox.cassandra.modules.CassandraDeletedMessageModule.class); cassandraDataDefinitions.addBinding().to(org.apache.james.mailbox.cassandra.modules.CassandraFirstUnseenModule.class); cassandraDataDefinitions.addBinding().to(org.apache.james.mailbox.cassandra.modules.CassandraMailboxCounterModule.class); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
