/**
* Copyright (c) 2011-2012 Optimax Software Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Optimax Software, ElasticInbox, nor the names
* of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.elasticinbox.core.cassandra.persistence;
import static com.elasticinbox.core.cassandra.CassandraDAOFactory.CF_METADATA;
import static me.prettyprint.hector.api.factory.HFactory.createColumn;
import static me.prettyprint.hector.api.factory.HFactory.createSuperColumn;
import static me.prettyprint.hector.api.factory.HFactory.createSuperSliceQuery;
import static me.prettyprint.hector.api.factory.HFactory.createMultigetSuperSliceQuery;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.elasticinbox.common.utils.Assert;
import com.elasticinbox.core.cassandra.CassandraDAOFactory;
import com.elasticinbox.core.model.Message;
import me.prettyprint.cassandra.serializers.BytesArraySerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.beans.HSuperColumn;
import me.prettyprint.hector.api.beans.SuperRows;
import me.prettyprint.hector.api.beans.SuperSlice;
import me.prettyprint.hector.api.exceptions.HectorException;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.MultigetSuperSliceQuery;
import me.prettyprint.hector.api.query.QueryResult;
import me.prettyprint.hector.api.query.SuperSliceQuery;
public final class MessagePersistence
{
private final static UUIDSerializer uuidSe = UUIDSerializer.get();
private final static StringSerializer strSe = StringSerializer.get();
private final static BytesArraySerializer byteSe = BytesArraySerializer.get();
private final static Logger logger =
LoggerFactory.getLogger(MessagePersistence.class);
/**
* Fetch attributes of multiple messages
*
* @param mailbox
* @param messageIds
* @param includeBody
* @return
*/
public static Map<UUID, Message> fetch(final String mailbox,
final Collection<UUID> messageIds, final boolean includeBody)
{
// read message ids from the result
Map<UUID, Message> result =
new LinkedHashMap<UUID, Message>(messageIds.size());
// Create a query
MultigetSuperSliceQuery<String, UUID, String, byte[]> q =
createMultigetSuperSliceQuery(CassandraDAOFactory.getKeyspace(), strSe, uuidSe, strSe, byteSe);
// set keys, cf, range
q.setColumnFamily(CF_METADATA);
q.setKeys(mailbox);
q.setColumnNames(messageIds);
// execute
QueryResult<SuperRows<String, UUID, String, byte[]>> r = q.execute();
SuperSlice<UUID, String, byte[]> slice =
r.get().getByKey(mailbox).getSuperSlice();
for (UUID messageId : messageIds)
{
if ((slice.getColumnByName(messageId) != null)
&& !slice.getColumnByName(messageId).getColumns().isEmpty())
{
result.put(messageId, Marshaller.unmarshall(
slice.getColumnByName(messageId).getColumns(), includeBody));
} else {
logger.debug(
"message {} not found in supercolumn slice for {} mailbox",
messageId, mailbox);
}
}
return result;
}
/**
* Fetch messsage attributes
*
* @param mailbox
* @param messageId
* @param includeBody
* @return
*/
public static Message fetch(final String mailbox, final UUID messageId,
final boolean includeBody)
{
List<UUID> messageIds = new ArrayList<UUID>(1);
messageIds.add(messageId);
Map<UUID, Message> messages = fetch(mailbox, messageIds, includeBody);
Assert.notNull(messages.get(messageId), "Message not found");
return messages.get(messageId);
}
/**
* Get messages within given range (excludes body)
*
* @param mailbox
* @param start
* @param count
* @return
*/
public static Map<UUID, Message> getRange(final String mailbox,
final UUID start, final int count)
{
// read message ids from the result
Map<UUID, Message> result = new LinkedHashMap<UUID, Message>();
// Create a query
SuperSliceQuery<String, UUID, String, byte[]> q =
createSuperSliceQuery(CassandraDAOFactory.getKeyspace(), strSe, uuidSe, strSe, byteSe);
// set keys, cf, range
q.setColumnFamily(CF_METADATA);
q.setKey(mailbox);
q.setRange(start, null, true, count);
// execute
QueryResult<SuperSlice<UUID, String, byte[]>> r = q.execute();
List<HSuperColumn<UUID, String, byte[]>> superColumns = r.get().getSuperColumns();
for (HSuperColumn<UUID, String, byte[]> superColumn : superColumns)
{
result.put(superColumn.getName(),
Marshaller.unmarshall(superColumn.getColumns(), false));
}
return result;
}
/**
* Persist {@link Message} object with given ID
*
* @param mailbox
* @param messageId
* @param message
* @throws IOException
*/
public static void persistMessage(Mutator<String> mutator, final String mailbox,
final UUID messageId, final Message message) throws IOException
{
logger.debug("Persisting metadata for message {} in mailbox {}",
messageId, mailbox);
mutator.addInsertion(mailbox, CF_METADATA, createSuperColumn(
messageId, Marshaller.marshall(message), uuidSe, strSe, byteSe));
}
/**
* Persist attributes for multiple messages
*
* @param mailbox
* @param messageIds
* @param attributes
* @throws HectorException
*/
private static void persistAttributes(Mutator<String> mutator, final String mailbox,
final List<UUID> messageIds, final Map<String, Object> attributes)
throws HectorException
{
List<HColumn<String, byte[]>> columns = Marshaller.mapToHColumns(attributes);
for (UUID messageId : messageIds) {
logger.debug("Persisting metadata for message {} in mailbox {}",
messageId.toString(), mailbox);
mutator.addInsertion(mailbox, CF_METADATA,
createSuperColumn(messageId, columns, uuidSe, strSe, byteSe));
}
}
/**
* Set flag attribute to multiple messages
*
* @param mailbox
* @param messageIds
* @param name
*/
public static void persistAttributes(Mutator<String> mutator, final String mailbox,
final List<UUID> messageIds, final Set<String> attributes)
{
Map<String, Object> attr = new HashMap<String, Object>(attributes.size());
for (String attribute : attributes) {
attr.put(attribute, new byte[0]);
}
persistAttributes(mutator, mailbox, messageIds, attr);
}
/**
* Set flag attribute to single message
*
* @param mailbox
* @param messageIds
* @param name
*/
public static void persistAttributes(Mutator<String> mutator, final String mailbox,
final UUID messageId, final Set<String> attributes)
{
List<UUID> messageIds = new ArrayList<UUID>(1);
messageIds.add(messageId);
persistAttributes(mutator, mailbox, messageIds, attributes);
}
/**
* Delete attributes from multiple messages
*
* @param mutator
* @param mailbox
* @param messageIds
* @param attributes
*/
public static void deleteAttributes(Mutator<String> mutator, final String mailbox,
final List<UUID> messageIds, final Set<String> attributes)
{
List<HColumn<String, byte[]>> columns =
new ArrayList<HColumn<String, byte[]>>(attributes.size());
for (String name : attributes) {
// FIXME: value should be null not "".
// see https://github.com/rantav/hector/issues/#issue/145
columns.add(createColumn(name, "".getBytes(), strSe, byteSe));
}
for (UUID messageId : messageIds) {
mutator.addSubDelete(mailbox, CF_METADATA,
createSuperColumn(messageId, columns, uuidSe, strSe, byteSe));
}
}
/**
* Delete attributes from single message
*
* @param mailbox
* @param messageIds
* @param name
*/
public static void deleteAttributes(Mutator<String> mutator, final String mailbox,
final UUID messageId, final Set<String> attributes)
{
List<UUID> messageIds = new ArrayList<UUID>(1);
messageIds.add(messageId);
deleteAttributes(mutator, mailbox, messageIds, attributes);
}
/**
* Delete single attribute from multiple messages
*
* @param mutator
* @param mailbox
* @param messageIds
* @param attribute
*/
public static void deleteAttribute(Mutator<String> mutator, final String mailbox,
final List<UUID> messageIds, final String attribute)
{
Set<String> names = new HashSet<String>(1);
names.add(attribute);
deleteAttributes(mutator, mailbox, messageIds, names);
}
/**
* Delete message and all its attributes
*
* @param mutator
* @param mailbox
* @param messageIds
*/
public static void deleteMessage(Mutator<String> mutator, final String mailbox,
final Collection<UUID> messageIds)
{
for (UUID messageId : messageIds) {
mutator.addDeletion(mailbox, CF_METADATA, messageId, uuidSe);
}
}
/**
* Delete all message metadata for account
*
* @param mutator
* @param mailbox
*/
public static void deleteAllMessages(Mutator<String> mutator, final String mailbox)
{
mutator.addDeletion(mailbox, CF_METADATA, null, strSe);
}
}