/** * 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 me.prettyprint.hector.api.factory.HFactory.createColumn; import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery; import static com.elasticinbox.core.cassandra.CassandraDAOFactory.CF_ACCOUNTS; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import com.elasticinbox.common.utils.Assert; import com.elasticinbox.core.IllegalLabelException; import com.elasticinbox.core.cassandra.CassandraDAOFactory; import com.elasticinbox.core.cassandra.utils.BatchConstants; import com.elasticinbox.core.model.Label; import com.elasticinbox.core.model.LabelMap; import com.elasticinbox.core.model.ReservedLabels; import me.prettyprint.cassandra.serializers.BytesArraySerializer; import me.prettyprint.cassandra.serializers.SerializerTypeInferer; import me.prettyprint.cassandra.serializers.StringSerializer; import me.prettyprint.hector.api.beans.ColumnSlice; import me.prettyprint.hector.api.beans.HColumn; import me.prettyprint.hector.api.mutation.Mutator; import me.prettyprint.hector.api.query.QueryResult; import me.prettyprint.hector.api.query.SliceQuery; public final class AccountPersistence { private final static String CN_LABEL_NAME_PREFIX = "label"; private final static String CN_LABEL_ATTRIBUTE_PREFIX = "lattr"; private final static String CN_SEPARATOR = ":"; private final static BytesArraySerializer byteSe = BytesArraySerializer.get(); private final static StringSerializer strSe = StringSerializer.get(); /** * Get all account attributes * * @param mailbox * @return * @throws IOException */ public static Map<String, Object> getAll(final String mailbox) { // Create a query SliceQuery<String, String, byte[]> q = createSliceQuery(CassandraDAOFactory.getKeyspace(), strSe, strSe, byteSe); // set key, cf, range q.setColumnFamily(CF_ACCOUNTS).setKey(mailbox); q.setRange(null, null, false, BatchConstants.BATCH_READS); // TODO: make sure we get all columns // execute QueryResult<ColumnSlice<String, byte[]>> r = q.execute(); // read attributes from the result Map<String, Object> attributes = new HashMap<String, Object>(); for (HColumn<String, byte[]> c : r.get().getColumns()) { if( (c != null) && (c.getValue() != null)) { attributes.put(c.getName(), strSe.fromBytes(c.getValue())); } } return attributes; } /** * Add or update account attributes (columns) * * @param account * @param attributes */ public static <V> void set(Mutator<String> mutator, final String mailbox, final Map<String, V> attributes) { if (!attributes.isEmpty()) { for (Map.Entry<String, V> a : attributes.entrySet()) { mutator.addInsertion(mailbox, CF_ACCOUNTS, createColumn(a.getKey(), a.getValue(), strSe, SerializerTypeInferer.getSerializer(a.getValue()))); } } } /** * Delete account * * @param mutator * @param mailbox */ public static void delete(Mutator<String> mutator, final String mailbox) { mutator.addDeletion(mailbox, CF_ACCOUNTS, null, strSe); } /** * Get all labels * * @param mailbox * @return */ public static LabelMap getLabels(final String mailbox) { LabelMap labels = new LabelMap(); // get list of user specific labels from Cassandra Map<String, Object> attributes = getAll(mailbox); // add user specific labels for (Map.Entry<String, Object> a : attributes.entrySet()) { if (a.getKey().startsWith(CN_LABEL_NAME_PREFIX)) { // set label name Integer labelId = Integer.parseInt(a.getKey().split(CN_SEPARATOR)[1]); String labelName = (String) a.getValue(); if (labels.containsId(labelId)) { labels.get(labelId).setName(labelName); } else { Label label = new Label(labelId, labelName); labels.put(label); } } else if (a.getKey().startsWith(CN_LABEL_ATTRIBUTE_PREFIX)) { // set label custom attribute String[] attrKeys = a.getKey().split(CN_SEPARATOR); Integer labelId = Integer.parseInt(attrKeys[1]); String attrName = attrKeys[2]; String attrValue = (String) a.getValue(); if (labels.containsId(labelId)) { labels.get(labelId).addAttribute(attrName, attrValue); } else { Label label = new Label(labelId); label.addAttribute(attrName, attrValue); labels.put(label); } } } // add default reserved labels for (Label l : ReservedLabels.getAll()) { Label label = new Label(l.getId(), l.getName()); labels.put(label); } return labels; } /** * Inserts new or updates existing label. * * @param mutator * @param mailbox * @param label */ public static void putLabel(Mutator<String> mutator, final String mailbox, Label label) { String labelKey = getLabelNameKey(label.getId()); Map<String, String> attributes = new HashMap<String, String>(1); // upsert label name if (label.getName() != null) { attributes.put(labelKey, label.getName()); } // upsert custom label attributes (delete if value is null or empty) if (label.getAttributes() != null) { for (Entry<String, String> labelAttr : label.getAttributes().entrySet()) { // custom label attribute should not contain separator char if (labelAttr.getKey().contains(CN_SEPARATOR)) { throw new IllegalLabelException("Invalid character detected in the custom label attribute name."); } String labelAttrKey = getLabelAttributeKey(label.getId(), labelAttr.getKey()); if (labelAttr.getValue() != null && !labelAttr.getValue().isEmpty()) { // upsert attribute attributes.put(labelAttrKey, labelAttr.getValue()); } else { // delete if value is empty mutator.addDeletion(mailbox, CF_ACCOUNTS, labelAttrKey, strSe); } } } AccountPersistence.set(mutator, mailbox, attributes); } /** * Delete label from account * * @param mutator * @param mailbox * @param labelId */ public static void deleteLabel(Mutator<String> mutator, final String mailbox, int labelId) { String labelNameKey = getLabelNameKey(labelId); String labelAttrPrefix = getLabelAttributeKey(labelId, ""); // label attributes prefix // delete label name mutator.addDeletion(mailbox, CF_ACCOUNTS, labelNameKey, strSe); // delete all attributes Map<String, Object> attributes = getAll(mailbox); for (String labelAttrKey : attributes.keySet()) { if (labelAttrKey.startsWith(labelAttrPrefix)) { mutator.addDeletion(mailbox, CF_ACCOUNTS, labelAttrKey, strSe); } } } /** * Generates key for custom label attribute. * <p> * Example <code>"lattr:123:MyAttribute"</code> * * @param labelId * @param attributeName Custom attribute name * @return */ static String getLabelAttributeKey(int labelId, final String attributeName) { Assert.notNull(attributeName, "Attribute name cannot be null"); return CN_LABEL_ATTRIBUTE_PREFIX + CN_SEPARATOR + labelId + CN_SEPARATOR + attributeName; } /** * Generates key for label name. * <p> * Example <code>"label:123"</code> * * @param labelId * @return */ static String getLabelNameKey(int labelId) { return CN_LABEL_NAME_PREFIX + CN_SEPARATOR + labelId; } }