/****************************************************************
* 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.hbase.mail;
import org.apache.hadoop.conf.Configuration;
import java.io.BufferedInputStream;
import org.apache.hadoop.hbase.client.Put;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.mail.Flags;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.hbase.io.ChunkOutputStream;
import org.apache.james.mailbox.model.MessageMetaData;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.UpdatedFlags;
import org.apache.james.mailbox.model.MessageRange.Type;
import org.apache.james.mailbox.store.SimpleMessageMetaData;
import org.apache.james.mailbox.store.transaction.NonTransactionalMapper;
import org.apache.james.mailbox.store.mail.MessageMapper;
import org.apache.james.mailbox.store.mail.ModSeqProvider;
import org.apache.james.mailbox.store.mail.UidProvider;
import org.apache.james.mailbox.store.mail.model.Mailbox;
import org.apache.james.mailbox.store.mail.model.Message;
import static org.apache.james.mailbox.hbase.HBaseUtils.*;
import static org.apache.james.mailbox.hbase.HBaseNames.*;
import static org.apache.james.mailbox.hbase.FlagConvertor.*;
/**
* HBase implementation of a {@link MessageMapper}.
* I don't know if this class is thread-safe! Asume it is not!
*
*/
public class HBaseMessageMapper extends NonTransactionalMapper implements MessageMapper<UUID> {
private final Configuration conf;
private final MailboxSession mailboxSession;
private final UidProvider<UUID> uidProvider;
private final ModSeqProvider<UUID> modSeqProvider;
public HBaseMessageMapper(final MailboxSession session,
final UidProvider<UUID> uidProvider,
ModSeqProvider<UUID> modSeqProvider, Configuration conf) {
this.mailboxSession = session;
this.modSeqProvider = modSeqProvider;
this.uidProvider = uidProvider;
this.conf = conf;
}
@Override
public void endRequest() {
}
@Override
public Iterator<Message<UUID>> findInMailbox(Mailbox<UUID> mailbox, MessageRange set, FetchType fType, int max) throws MailboxException {
try {
List<Message<UUID>> results;
long from = set.getUidFrom();
final long to = set.getUidTo();
final Type type = set.getType();
switch (type) {
default:
case ALL:
results = findMessagesInMailbox(mailbox, max, false);
break;
case FROM:
results = findMessagesInMailboxAfterUID(mailbox, from, max, false);
break;
case ONE:
results = findMessagesInMailboxWithUID(mailbox, from, false);
break;
case RANGE:
results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max, false);
break;
}
return results.iterator();
} catch (IOException e) {
throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e);
}
}
private List<Message<UUID>> findMessagesInMailbox(Mailbox<UUID> mailbox, int batchSize, boolean flaggedForDelete) throws IOException {
List<Message<UUID>> messageList = new ArrayList<Message<UUID>>();
HTable messages = new HTable(conf, MESSAGES_TABLE);
Scan scan = new Scan(customMessageRowKey(mailbox.getMailboxId(), 0L),
new PrefixFilter(Bytes.add(Bytes.toBytes(mailbox.getMailboxId().getMostSignificantBits()),
Bytes.toBytes(mailbox.getMailboxId().getLeastSignificantBits()))));
if (flaggedForDelete) {
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT);
filter.setFilterIfMissing(true);
scan.setFilter(filter);
}
scan.setMaxVersions(1);
/* we exclude the message content column family because it could be too large.
* the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation.
*/
scan.addFamily(MESSAGES_META_CF);
ResultScanner scanner = messages.getScanner(scan);
Result result;
long count = batchSize > 0 ? batchSize : Long.MAX_VALUE;
while (((result = scanner.next()) != null) && (count > 0)) {
messageList.add(messageMetaFromResult(conf, result));
count--;
}
scanner.close();
messages.close();
// we store uids in reverse order, we send them ascending
Collections.reverse(messageList);
return messageList;
}
private List<Message<UUID>> findMessagesInMailboxWithUID(Mailbox<UUID> mailbox, final long messageUid, final boolean flaggedForDelete) throws IOException {
List<Message<UUID>> messageList = new ArrayList<Message<UUID>>();
HTable messages = new HTable(conf, MESSAGES_TABLE);
Get get = new Get(messageRowKey(mailbox.getMailboxId(), messageUid));
get.setMaxVersions(1);
/* we exclude the message content column family because it could be too large.
* the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation.
*/
if (flaggedForDelete) {
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT);
filter.setFilterIfMissing(true);
get.setFilter(filter);
}
get.addFamily(MESSAGES_META_CF);
Result result = messages.get(get);
Message<UUID> message = null;
if (!result.isEmpty()) {
message = messageMetaFromResult(conf, result);
messageList.add(message);
}
messages.close();
return messageList;
}
private List<Message<UUID>> findMessagesInMailboxAfterUID(Mailbox<UUID> mailbox, final long from, final int batchSize, final boolean flaggedForDelete) throws IOException {
List<Message<UUID>> messageList = new ArrayList<Message<UUID>>();
HTable messages = new HTable(conf, MESSAGES_TABLE);
// uids are stored in reverse so we need to search
Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE),
messageRowKey(mailbox.getMailboxId(), from - 1));
if (flaggedForDelete) {
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT);
filter.setFilterIfMissing(true);
scan.setFilter(filter);
}
scan.setMaxVersions(1);
/* we exclude the message content column family because it could be too large.
* the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation.
*/
scan.addFamily(MESSAGES_META_CF);
ResultScanner scanner = messages.getScanner(scan);
Result result;
long count = batchSize > 0 ? batchSize : Long.MAX_VALUE;
while (((result = scanner.next()) != null) && (count > 0)) {
messageList.add(messageMetaFromResult(conf, result));
count--;
}
scanner.close();
messages.close();
// uids are stored in reverese so we change the list
Collections.reverse(messageList);
return messageList;
}
private List<Message<UUID>> findMessagesInMailboxBetweenUIDs(Mailbox<UUID> mailbox, final long from, final long to, final int batchSize, final boolean flaggedForDelete) throws IOException {
List<Message<UUID>> messageList = new ArrayList<Message<UUID>>();
if (from > to) {
return messageList;
}
HTable messages = new HTable(conf, MESSAGES_TABLE);
/*TODO: check if Between should be inclusive or exclusive regarding limits.
* HBase scan operaion are exclusive to the upper bound when providing stop row key.
*/
Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), to), messageRowKey(mailbox.getMailboxId(), from - 1));
if (flaggedForDelete) {
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT);
filter.setFilterIfMissing(true);
scan.setFilter(filter);
}
scan.setMaxVersions(1);
/* we exclude the message content column family because it could be too large.
* the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation.
*/
scan.addFamily(MESSAGES_META_CF);
ResultScanner scanner = messages.getScanner(scan);
Result result;
long count = batchSize > 0 ? batchSize : Long.MAX_VALUE;
while (((result = scanner.next()) != null)) {
if (count == 0) {
break;
}
Message<UUID> message = messageMetaFromResult(conf, result);
messageList.add(message);
count--;
}
scanner.close();
messages.close();
// uids are stored in reverse order
Collections.reverse(messageList);
return messageList;
}
@Override
public Map<Long, MessageMetaData> expungeMarkedForDeletionInMailbox(Mailbox<UUID> mailbox, MessageRange set) throws MailboxException {
try {
final Map<Long, MessageMetaData> data;
final List<Message<UUID>> results;
final long from = set.getUidFrom();
final long to = set.getUidTo();
switch (set.getType()) {
case ONE:
results = findMessagesInMailboxWithUID(mailbox, from, true);
data = createMetaData(results);
deleteDeletedMessagesInMailboxWithUID(mailbox, from);
break;
case RANGE:
results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, -1, true);
data = createMetaData(results);
deleteDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to);
break;
case FROM:
results = findMessagesInMailboxAfterUID(mailbox, from, -1, true);
data = createMetaData(results);
deleteDeletedMessagesInMailboxAfterUID(mailbox, from);
break;
default:
case ALL:
results = findMessagesInMailbox(mailbox, -1, true);
data = createMetaData(results);
deleteDeletedMessagesInMailbox(mailbox);
break;
}
return data;
} catch (IOException e) {
throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e);
}
}
@Override
public long countMessagesInMailbox(Mailbox<UUID> mailbox) throws MailboxException {
HTable mailboxes = null;
try {
mailboxes = new HTable(conf, MAILBOXES_TABLE);
Get get = new Get(mailboxRowKey(mailbox.getMailboxId()));
get.addColumn(MAILBOX_CF, MAILBOX_MESSAGE_COUNT);
get.setMaxVersions(1);
Result result = mailboxes.get(get);
long count = Bytes.toLong(result.getValue(MAILBOX_CF, MAILBOX_MESSAGE_COUNT));
return count;
} catch (IOException e) {
throw new MailboxException("Count of messages failed in mailbox " + mailbox, e);
} finally {
if (mailboxes != null) {
try {
mailboxes.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + mailboxes, ex);
}
}
}
}
@Override
public long countUnseenMessagesInMailbox(Mailbox<UUID> mailbox) throws MailboxException {
/* TODO: see if it is possible to store the number of unseen messages in the mailbox table
* and just return that value with a Get and kepp it up to date.
*/
HTable messages = null;
ResultScanner scanner = null;
try {
messages = new HTable(conf, MESSAGES_TABLE);
/* Limit the number of entries scanned to just the mails in this mailbox */
Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE),
messageRowKey(mailbox.getMailboxId(), 0));
scan.addFamily(MESSAGES_META_CF);
scan.setFilter(new SingleColumnValueExcludeFilter(MESSAGES_META_CF, FLAGS_SEEN, CompareOp.EQUAL, MARKER_MISSING));
scan.setCaching(messages.getScannerCaching() * 2);
scan.setMaxVersions(1);
scanner = messages.getScanner(scan);
long count = 0;
Result result;
while ((result = scanner.next()) != null) {
count++;
}
return count;
} catch (IOException e) {
throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e);
} finally {
scanner.close();
if (messages != null) {
try {
messages.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + messages, ex);
}
}
}
}
@Override
public void delete(Mailbox<UUID> mailbox, Message<UUID> message) throws MailboxException {
//TODO: maybe switch to checkAndDelete
HTable messages = null;
HTable mailboxes = null;
try {
messages = new HTable(conf, MESSAGES_TABLE);
mailboxes = new HTable(conf, MAILBOXES_TABLE);
/** TODO: also implement/update the message count for this mailbox
* and implement countMessages with get.
*/
Delete delete = new Delete(messageRowKey(message));
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -1);
messages.delete(delete);
} catch (IOException ex) {
throw new MailboxException("Delete of message " + message + " failed in mailbox " + mailbox, ex);
} finally {
if (mailboxes != null) {
try {
mailboxes.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + mailboxes, ex);
}
}
if (messages != null) {
try {
messages.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + messages, ex);
}
}
}
}
@Override
public Long findFirstUnseenMessageUid(Mailbox<UUID> mailbox) throws MailboxException {
HTable messages = null;
ResultScanner scanner = null;
try {
messages = new HTable(conf, MESSAGES_TABLE);
/* Limit the number of entries scanned to just the mails in this mailbox */
Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), messageRowKey(mailbox.getMailboxId(), 0));
scan.addFamily(MESSAGES_META_CF);
// filter out all rows with FLAGS_SEEN qualifier
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_SEEN, CompareOp.EQUAL, MARKER_MISSING);
scan.setFilter(filter);
scan.setCaching(messages.getScannerCaching() * 2);
scan.setMaxVersions(1);
scanner = messages.getScanner(scan);
Result result;
Long lastUnseen = null;
byte[] row = null;
while ((result = scanner.next()) != null) {
row = result.getRow();
}
if (row != null) {
lastUnseen = Long.MAX_VALUE - Bytes.toLong(row, 16, 8);
}
return lastUnseen;
} catch (IOException e) {
throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e);
} finally {
scanner.close();
if (messages != null) {
try {
messages.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + messages, ex);
}
}
}
}
@Override
public List<Long> findRecentMessageUidsInMailbox(Mailbox<UUID> mailbox) throws MailboxException {
/** TODO: improve performance by implementing a last seen and last recent value per mailbox.
* maybe one more call to HBase is less expensive than iterating throgh all rows.
*/
HTable messages = null;
ResultScanner scanner = null;
try {
messages = new HTable(conf, MESSAGES_TABLE);
/* Limit the number of entries scanned to just the mails in this mailbox */
Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE),
messageRowKey(mailbox.getMailboxId(), 0));
// we add the column, if it exists, the message is recent, else it is not
scan.addColumn(MESSAGES_META_CF, FLAGS_RECENT);
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_RECENT, CompareOp.EQUAL, MARKER_PRESENT);
scan.setFilter(filter);
scan.setCaching(messages.getScannerCaching() * 2);
scan.setMaxVersions(1);
scanner = messages.getScanner(scan);
Result result;
List<Long> uids = new ArrayList<Long>();
while ((result = scanner.next()) != null) {
uids.add(Long.MAX_VALUE - Bytes.toLong(result.getRow(), 16, 8));
}
Collections.reverse(uids);
return uids;
} catch (IOException e) {
throw new MailboxException("Search of recent messages failed in mailbox " + mailbox, e);
} finally {
scanner.close();
if (messages != null) {
try {
messages.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + messages, ex);
}
}
}
}
/*
* (non-Javadoc)
* @see org.apache.james.mailbox.store.mail.MessageMapper#add(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message)
*/
@Override
public MessageMetaData add(Mailbox<UUID> mailbox, Message<UUID> message) throws MailboxException {
message.setUid(uidProvider.nextUid(mailboxSession, mailbox));
// if a mailbox does not support mod-sequences the provider may be null
if (modSeqProvider != null) {
message.setModSeq(modSeqProvider.nextModSeq(mailboxSession, mailbox));
}
MessageMetaData data = save(mailbox, message);
return data;
}
/*
* (non-Javadoc)
* @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, javax.mail.Flags, boolean, boolean, org.apache.james.mailbox.MessageRange)
*/
@Override
public Iterator<UpdatedFlags> updateFlags(final Mailbox<UUID> mailbox, final Flags flags, final boolean value, final boolean replace, MessageRange set) throws MailboxException {
final List<UpdatedFlags> updatedFlags = new ArrayList<UpdatedFlags>();
Iterator<Message<UUID>> messagesFound = findInMailbox(mailbox, set, FetchType.Metadata, -1);
HTable messages = null;
long modSeq = -1;
if (messagesFound.hasNext() == false) {
// if a mailbox does not support mod-sequences the provider may be null
if (modSeqProvider != null) {
modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox);
}
}
try {
messages = new HTable(conf, MESSAGES_TABLE);
while (messagesFound.hasNext()) {
Put put = null;
final Message<UUID> member = messagesFound.next();
Flags originalFlags = member.createFlags();
if (replace) {
member.setFlags(flags);
} else {
Flags current = member.createFlags();
if (value) {
current.add(flags);
} else {
current.remove(flags);
}
member.setFlags(current);
}
Flags newFlags = member.createFlags();
put = flagsToPut(member, newFlags);
if (UpdatedFlags.flagsChanged(originalFlags, newFlags)) {
// increase the mod-seq as we changed the flags
put.add(MESSAGES_META_CF, MESSAGE_MODSEQ, Bytes.toBytes(modSeq));
// update put not to include the allready existing flags
messages.put(put);
messages.flushCommits();
}
UpdatedFlags uFlags = new UpdatedFlags(member.getUid(), member.getModSeq(), originalFlags, newFlags);
updatedFlags.add(uFlags);
}
} catch (IOException e) {
throw new MailboxException("Error setting flags for messages in " + mailbox, e);
} finally {
if (messages != null) {
try {
messages.close();
} catch (IOException e) {
throw new MailboxException("Error setting flags for messages in " + mailbox, e);
}
}
}
return updatedFlags.iterator();
}
/*
* (non-Javadoc)
* @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message)
*/
@Override
public MessageMetaData copy(Mailbox<UUID> mailbox, Message<UUID> original) throws MailboxException {
long uid = uidProvider.nextUid(mailboxSession, mailbox);
long modSeq = -1;
if (modSeqProvider != null) {
modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox);
}
//TODO: check if creating a HBase message is the right thing to do
HBaseMessage message = new HBaseMessage(conf,
mailbox.getMailboxId(), uid, modSeq, original);
return save(mailbox, message);
}
/*
* (non-Javadoc)
* @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message)
*/
@Override
public MessageMetaData move(Mailbox<UUID> mailbox, Message<UUID> original) throws MailboxException {
//TODO implement if possible
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.apache.james.mailbox.store.mail.MessageMapper#getLastUid(org.apache.james.mailbox.store.mail.model.Mailbox)
*/
@Override
public long getLastUid(Mailbox<UUID> mailbox) throws MailboxException {
return uidProvider.lastUid(mailboxSession, mailbox);
}
/*
* (non-Javadoc)
* @see org.apache.james.mailbox.store.mail.MessageMapper#getHighestModSeq(org.apache.james.mailbox.store.mail.model.Mailbox)
*/
@Override
public long getHighestModSeq(Mailbox<UUID> mailbox) throws MailboxException {
return modSeqProvider.highestModSeq(mailboxSession, mailbox);
}
/**
* Save the {@link Message} for the given {@link Mailbox} and return the {@link MessageMetaData}
*
* @param mailbox
* @param message
* @return metaData
* @throws MailboxException
*/
protected MessageMetaData save(Mailbox<UUID> mailbox, Message<UUID> message) throws MailboxException {
HTable messages = null;
HTable mailboxes = null;
BufferedInputStream in = null;
ChunkOutputStream out = null;
try {
//TODO: update the mailbox information about messages
messages = new HTable(conf, MESSAGES_TABLE);
mailboxes = new HTable(conf, MAILBOXES_TABLE);
//save the message metadata
Put put = metadataToPut(message);
messages.put(put);
//save the message content
//TODO: current implementation is crude.
int b;
out = new ChunkOutputStream(conf,
MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, messageRowKey(message), MAX_COLUMN_SIZE);
in = new BufferedInputStream(message.getBodyContent());
while ((b = in.read()) != -1) {
out.write(b);
}
in.close();
out.close();
out = new ChunkOutputStream(conf,
MESSAGES_TABLE, MESSAGE_DATA_HEADERS_CF, messageRowKey(message), MAX_COLUMN_SIZE);
in = new BufferedInputStream(message.getHeaderContent());
while ((b = in.read()) != -1) {
out.write(b);
}
in.close();
out.close();
// increase the message count for the current mailbox
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, 1);
return new SimpleMessageMetaData(message);
} catch (IOException ex) {
throw new MailboxException("Error setting flags for messages in " + mailbox, ex);
} finally {
if (messages != null) {
try {
messages.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + messages, ex);
}
}
if (mailboxes != null) {
try {
mailboxes.close();
} catch (IOException ex) {
throw new MailboxException("Error closing table " + mailboxes, ex);
}
}
if (in != null) {
try {
in.close();
} catch (IOException ex) {
throw new MailboxException("Error closing Inputtream", ex);
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
throw new MailboxException("Error closing OutputStream", ex);
}
}
}
}
private void deleteDeletedMessagesInMailboxWithUID(Mailbox<UUID> mailbox, long uid) throws IOException {
//TODO: do I have to check if the message is flagged for delete here?
HTable messages = new HTable(conf, MESSAGES_TABLE);
HTable mailboxes = new HTable(conf, MAILBOXES_TABLE);
Delete delete = new Delete(messageRowKey(mailbox.getMailboxId(), uid));
messages.delete(delete);
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -1);
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1);
mailboxes.close();
messages.close();
}
private void deleteDeletedMessagesInMailboxBetweenUIDs(Mailbox<UUID> mailbox, long fromUid, long toUid) throws IOException {
HTable messages = new HTable(conf, MESSAGES_TABLE);
HTable mailboxes = new HTable(conf, MAILBOXES_TABLE);
List<Delete> deletes = new ArrayList<Delete>();
/*TODO: check if Between should be inclusive or exclusive regarding limits.
* HBase scan operaion are exclusive to the upper bound when providing stop row key.
*/
Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), fromUid), messageRowKey(mailbox.getMailboxId(), toUid));
scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED);
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT);
scan.setFilter(filter);
scan.setMaxVersions(1);
ResultScanner scanner = messages.getScanner(scan);
Result result;
while ((result = scanner.next()) != null) {
deletes.add(new Delete(result.getRow()));
}
long totalDeletes = deletes.size();
scanner.close();
messages.delete(deletes);
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size()));
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1);
mailboxes.close();
messages.close();
}
private void deleteDeletedMessagesInMailboxAfterUID(Mailbox<UUID> mailbox, long fromUid) throws IOException {
HTable messages = new HTable(conf, MESSAGES_TABLE);
HTable mailboxes = new HTable(conf, MAILBOXES_TABLE);
List<Delete> deletes = new ArrayList<Delete>();
/*TODO: check if Between should be inclusive or exclusive regarding limits.
* HBase scan operaion are exclusive to the upper bound when providing stop row key.
*/
Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), fromUid));
scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED);
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT);
scan.setFilter(filter);
scan.setMaxVersions(1);
ResultScanner scanner = messages.getScanner(scan);
Result result;
while ((result = scanner.next()) != null) {
deletes.add(new Delete(result.getRow()));
}
long totalDeletes = deletes.size();
scanner.close();
messages.delete(deletes);
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size()));
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1);
mailboxes.close();
messages.close();
}
private void deleteDeletedMessagesInMailbox(Mailbox<UUID> mailbox) throws IOException {
HTable messages = new HTable(conf, MESSAGES_TABLE);
HTable mailboxes = new HTable(conf, MAILBOXES_TABLE);
List<Delete> deletes = new ArrayList<Delete>();
/*TODO: check if Between should be inclusive or exclusive regarding limits.
* HBase scan operaion are exclusive to the upper bound when providing stop row key.
*/
Scan scan = new Scan(customMessageRowKey(mailbox.getMailboxId(), 0L),
new PrefixFilter(Bytes.add(Bytes.toBytes(mailbox.getMailboxId().getMostSignificantBits()),
Bytes.toBytes(mailbox.getMailboxId().getLeastSignificantBits()))));
scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED);
SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT);
scan.setFilter(filter);
scan.setMaxVersions(1);
ResultScanner scanner = messages.getScanner(scan);
Result result;
while ((result = scanner.next()) != null) {
deletes.add(new Delete(result.getRow()));
}
long totalDeletes = deletes.size();
scanner.close();
messages.delete(deletes);
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size()));
mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1);
mailboxes.close();
messages.close();
}
private Map<Long, MessageMetaData> createMetaData(List<Message<UUID>> uids) {
final Map<Long, MessageMetaData> data = new HashMap<Long, MessageMetaData>();
for (int i = 0; i < uids.size(); i++) {
Message<UUID> m = uids.get(i);
data.put(m.getUid(), new SimpleMessageMetaData(m));
}
return data;
}
}