/**
* 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;
import static me.prettyprint.hector.api.factory.HFactory.createMutator;
import java.io.IOException;
import java.util.HashSet;
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.core.ExistingLabelException;
import com.elasticinbox.core.IllegalLabelException;
import com.elasticinbox.core.LabelDAO;
import com.elasticinbox.core.MessageDAO;
import com.elasticinbox.core.MessageModification;
import com.elasticinbox.core.cassandra.persistence.AccountPersistence;
import com.elasticinbox.core.cassandra.persistence.LabelCounterPersistence;
import com.elasticinbox.core.cassandra.persistence.LabelIndexPersistence;
import com.elasticinbox.core.cassandra.utils.BatchConstants;
import com.elasticinbox.core.model.Label;
import com.elasticinbox.core.model.LabelCounters;
import com.elasticinbox.core.model.LabelMap;
import com.elasticinbox.core.model.Mailbox;
import com.elasticinbox.core.model.ReservedLabels;
import com.elasticinbox.core.utils.LabelUtils;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.mutation.Mutator;
public final class CassandraLabelDAO implements LabelDAO
{
private final Keyspace keyspace;
private final static StringSerializer strSe = StringSerializer.get();
private final static Logger logger =
LoggerFactory.getLogger(CassandraLabelDAO.class);
public CassandraLabelDAO(Keyspace keyspace) {
this.keyspace = keyspace;
}
@Override
public LabelMap getAllWithMetadata(final Mailbox mailbox)
throws IOException
{
// get labels
LabelMap labels = AccountPersistence.getLabels(mailbox.getId());
// set labels' counters
Map<Integer, LabelCounters> counters = LabelCounterPersistence.getAll(mailbox.getId());
for (int labelId : counters.keySet())
{
if (labels.containsId(labelId) && counters.containsKey(labelId)) {
labels.get(labelId).setCounters(counters.get(labelId));
} else if (labels.containsId(labelId) && !counters.containsKey(labelId)) {
// assume zeros for all counters if not yet initialised
labels.get(labelId).setCounters(new LabelCounters());
} else if (!labels.containsId(labelId) && counters.containsKey(labelId)) {
logger.warn("Found counters for label {}/{}, but label does not exist.", mailbox.getId(), labelId);
}
}
return labels;
}
@Override
public Map<Integer, String> getAll(final Mailbox mailbox) {
return AccountPersistence.getLabels(mailbox.getId()).getNameMap();
}
@Override
public int add(Mailbox mailbox, Label label)
{
// get all existing labels
LabelMap existingLabels = AccountPersistence.getLabels(mailbox.getId());
LabelUtils.validateLabelName(label.getName(), existingLabels);
try {
// generate new label id
int labelId = LabelUtils.getNewLabelId(existingLabels.getIds());
label.setId(labelId);
} catch (IllegalLabelException ile) {
// log and rethrow
logger.warn("{} reached max random label id attempts with {} labels",
mailbox, existingLabels.size());
throw ile;
}
// begin batch operation
Mutator<String> mutator = createMutator(keyspace, strSe);
// add new label
AccountPersistence.putLabel(mutator, mailbox.getId(), label);
// commit batch operation
mutator.execute();
return label.getId();
}
@Override
public void update(Mailbox mailbox, Label label) throws IOException
{
// get all existing labels
LabelMap existingLabels = AccountPersistence.getLabels(mailbox.getId());
// validate only if name is changed (skips letter case changes)
if (label.getName() != null && !existingLabels.containsName(label.getName())) {
LabelUtils.validateLabelName(label.getName(), existingLabels);
}
// check if label id reserved
if (ReservedLabels.contains(label.getId())) {
throw new ExistingLabelException("This is reserved label and can't be modified");
}
// check if label id exists
if (!existingLabels.containsId(label.getId())) {
throw new IllegalLabelException("Label does not exist");
}
// begin batch operation
Mutator<String> mutator = createMutator(keyspace, strSe);
// set new name
AccountPersistence.putLabel(mutator, mailbox.getId(), label);
// commit batch operation
mutator.execute();
}
@Override
public void delete(final Mailbox mailbox, final Integer labelId)
{
// check if label reserved
if(ReservedLabels.contains(labelId)) {
throw new IllegalLabelException("This is reserved label and can't be modified");
}
// get message DAO object
MessageDAO messageDAO = new CassandraMessageDAO(keyspace);
List<UUID> messageIds = null;
Set<Integer> labelIds = new HashSet<Integer>(1);
labelIds.add(labelId);
// loop until we delete all items
do {
// get message ids of label
messageIds = LabelIndexPersistence.get(mailbox.getId(), labelId,
null, BatchConstants.BATCH_READS, false);
// remove label from message metadata
messageDAO.modify(mailbox, messageIds,
new MessageModification.Builder().removeLabels(labelIds).build());
}
while (messageIds.size() >= BatchConstants.BATCH_READS);
// begin batch operation
Mutator<String> m = createMutator(keyspace, strSe);
// delete label index
LabelIndexPersistence.deleteIndex(m, mailbox.getId(), labelId);
// delete label counters
LabelCounterPersistence.delete(m, mailbox.getId(), labelId);
// delete label info from account mailbox
AccountPersistence.deleteLabel(m, mailbox.getId(), labelId);
// commit batch operation
m.execute();
}
@Override
public void setCounters(Mailbox mailbox, LabelMap newCounters)
{
Map<Integer, LabelCounters> existingCounters =
LabelCounterPersistence.getAll(mailbox.getId());
// begin batch operation
Mutator<String> m = createMutator(keyspace, strSe);
// update with the new counter values
for (Label label : newCounters.values())
{
int labelId = label.getId();
LabelCounters diff = new LabelCounters(label.getCounters());
if (existingCounters.containsKey(labelId)) {
diff.add(existingCounters.get(labelId).getInverse());
}
logger.debug(
"Recalculated counters for label {}:\n\tCurrent: {}\n\tCalculated: {}\n\tDiff: {}",
new Object[] { labelId, existingCounters.get(labelId), label.getCounters(), diff });
LabelCounterPersistence.add(m, mailbox.getId(), labelId, diff);
}
// reset non-existing counters
for (int labelId : existingCounters.keySet())
{
if (!newCounters.containsId(labelId)) {
LabelCounterPersistence.subtract(
m, mailbox.getId(), labelId, existingCounters.get(labelId));
}
}
m.execute();
}
}