All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ebuddy.cassandra.NestedBatchMutation Maven / Gradle / Ivy

There is a newer version: 2.4.2
Show newest version
/*
 * Copyright 2013 eBuddy B.V.
 *
 *    Licensed 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 com.ebuddy.cassandra;

import static com.ebuddy.cassandra.HectorUtils.bytes;
import static com.ebuddy.cassandra.HectorUtils.getSlice;
import static com.ebuddy.cassandra.HectorUtils.string;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.Mutation;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SuperColumn;
import org.apache.commons.collections.KeyValue;
import org.apache.commons.collections.keyvalue.DefaultKeyValue;

import com.ebuddy.cassandra.property.PropertyValue;
import com.ebuddy.cassandra.property.PropertyValueFactory;

/**
 * An NestedBatchMutation encapsulates a set of updates/insertions/deletions all submitted
 * at the same time to Cassandra.
 * 

* Similar to the Hector BatchMutation, but this implementation also supports a delta * insertion to an embedded Map object, to support hierarchically nested properties. *

* A NestedBatchMutation is also limited to a single row and single column family. * * @author Eric Zoerner [email protected] * @deprecated use DAO objects and ExtendedNetworkId instead * */ @Deprecated class NestedBatchMutation { private final CassandraTemplate cassandraTemplate; private final String keySpace; private final String rowKey; private final String columnFamily; private final String superColumnName; // may be null /** map of mutations, new values keyed by column name. */ private final Map mutations = new HashMap(); /** map of partial mutations, keyed by column name. */ private final Map partials = new HashMap(); private boolean partialsApplied = false; /** * Construct a NestedBatchMutation. * * @param cassandraTemplate * @param keySpace * @param rowKey the row key * @param columnFamily the column family * @param superColumnName the super column name, or null if this is a regular column family */ NestedBatchMutation(CassandraTemplate cassandraTemplate, String keySpace, String rowKey, String columnFamily, String superColumnName) { this.cassandraTemplate = cassandraTemplate; this.keySpace = keySpace; if(rowKey == null) { throw new IllegalArgumentException("rowKey should not be null"); } if (columnFamily == null) { throw new IllegalArgumentException("columnFamily should not be null"); } this.rowKey = rowKey; this.columnFamily = columnFamily; this.superColumnName = superColumnName; } /** * Add an Column insertion (or update) to the batch mutation request. * * @param path the property name, can be hierarchical using delimiter * @param value the new String value for the property * @return the receiver */ NestedBatchMutation addInsertion(String path, String value, String delimiter) { if (partialsApplied) { throw new IllegalStateException("cannot insert after getMutationMap() is called"); } if (path.indexOf(delimiter) >= 0) { // first part of path is the column name, extract rest for the relative path String[] parts = splitFastAndURLDecode(path,delimiter); int restLen = parts.length - 1; String[] rest = new String[restLen]; System.arraycopy(parts, 1, rest, 0, restLen); insertPartialMutation(parts[0], rest, value); } else { mutations.put(urlDecode(path), bytes(value)); } return this; } /** * Calculate and return the mutation map for updating Cassandra. * For hierarchical properties, reads the current value from Cassandra, then applies the nested properties to it. * * @return the mutation map */ Map>> getMutationMap() { if (!partials.isEmpty()) { fillInOldValues(); buildMutationsFromPartials(); } return Collections.unmodifiableMap(createMutationMapIgnoringPartials()); } private void fillInOldValues() { // incorporate the partial mutations into the list of mutations. // The old values need to be retrieved first with a single // getSlice. Build up a SlicePredicate to fill in the old values. SlicePredicate slicePredicate = new SlicePredicate(); for (Map.Entry partialKeyValue : partials.entrySet()) { // the columnName is the key of the map entry String columnName = partialKeyValue.getKey(); // add this column to the predicate slicePredicate.addToColumn_names(bytes(columnName)); } // read old values from Cassandra List columns = getSlice(rowKey, columnFamily, superColumnName, slicePredicate, cassandraTemplate, keySpace); for (Column column : columns) { String columnName = string(column.getName()); partials.get(columnName).oldValue = column.bufferForValue(); } } private void buildMutationsFromPartials() { for (Map.Entry partialKeyValue : partials.entrySet()) { PartialMutation partialMutation = partialKeyValue.getValue(); PropertyValue oldValuePropertyValue = null; ByteBuffer oldValueBytes = partialMutation.oldValue; if (oldValueBytes != null) { oldValuePropertyValue = PropertyValueFactory.get().createPropertyValue(oldValueBytes); } Map> topMap; if (oldValuePropertyValue == null || !oldValuePropertyValue.isNestedProperties()) { // if not nested properties, then storing a nested property ON TOP of a non-nested value. // in this case, ignore the old value, will be overwritten with a new NestedProperties partialMutation.oldValue = null; topMap = new HashMap>(); } else { @SuppressWarnings({"unchecked"}) PropertyValue>> nestedProps = (PropertyValue>>)oldValuePropertyValue; topMap = nestedProps.getValue(); } ByteBuffer newValue = applyPathsToNestedMaps(partialMutation.relativePaths, topMap); this.mutations.put(partialKeyValue.getKey(), newValue); } // remember that partials have been applied so no new partials can be added partialsApplied = true; } /** * Apply relative paths to nested maps and return the new value for this column as a byte[]. * * @param relativePaths the relative paths with values to apply * @param topMap the top nested map * @return the byte[] for new column value */ private ByteBuffer applyPathsToNestedMaps(List relativePaths, Map> topMap) { for (KeyValue relativePathKeyValue : relativePaths) { applyPathToNestedMap(relativePathKeyValue, topMap); } return PropertyValueFactory.get().createPropertyValue(topMap).toBytes(); } // modified topMap in place private void applyPathToNestedMap(KeyValue relativePathKeyValue, Map> topMap) { String[] parts = (String[])relativePathKeyValue.getKey(); Map> thisMap = topMap; String lastPart = parts[parts.length - 1]; // key for leaf value for (String part : parts) { if (part.equals(lastPart)) { // on the leaf part, so store in map here thisMap.put(part, PropertyValueFactory.get().createPropertyValue((String)relativePathKeyValue.getValue())); } else { PropertyValue nextValue = thisMap.get(part); PropertyValue>> nextNestedPropertyValue; if (nextValue == null || !nextValue.isNestedProperties()) { // unable to drill down here, so overwrite whatever was there with new map Map> newMap = new HashMap>(); nextNestedPropertyValue = PropertyValueFactory.get().createPropertyValue(newMap); thisMap.put(part, nextNestedPropertyValue); } else { //noinspection unchecked nextNestedPropertyValue = (PropertyValue>>)nextValue; } // drill down thisMap = nextNestedPropertyValue.getValue(); } } } /** * Build a standard Cassandra mutation map out of the list of mutations. The partials are assumed * to have already been applied. * * @see #getMutationMap * @return mutation map */ private Map>> createMutationMapIgnoringPartials() { assert partials.isEmpty() || partialsApplied; Map>> result = new HashMap>>(); Map> innerMutationMap = new HashMap>(); List mutationList = new ArrayList(mutations.size()); long timestamp = cassandraTemplate.createTimestamp(); if (superColumnName != null) { List columns = new ArrayList(); Mutation mutation = new Mutation(); for (Map.Entry entry : mutations.entrySet()) { Column column = new Column(bytes(entry.getKey())); column.setValue(entry.getValue()); column.setTimestamp(timestamp); columns.add(column); } SuperColumn superColumn = new SuperColumn(bytes(superColumnName), columns); mutation.setColumn_or_supercolumn(new ColumnOrSuperColumn().setSuper_column(superColumn)); mutationList.add(mutation); } else { for (Map.Entry entry : mutations.entrySet()) { Column column = new Column(bytes(entry.getKey())); column.setValue(entry.getValue()); column.setTimestamp(timestamp); Mutation mutation = new Mutation(); mutation.setColumn_or_supercolumn(new ColumnOrSuperColumn().setColumn(column)); mutationList.add(mutation); } } innerMutationMap.put(columnFamily, mutationList); result.put(bytes(rowKey), innerMutationMap); return result; } private void insertPartialMutation(String columnName, String[] relativePath, String newValue) { // see if there is an existing partial mutation PartialMutation partialMutation = partials.get(columnName); if (partialMutation == null) { partialMutation = new PartialMutation(); partials.put(columnName, partialMutation); } partialMutation.relativePaths.add(new DefaultKeyValue(relativePath, newValue)); } private static String[] splitFastAndURLDecode(String toSplit,String delimiter) { StringTokenizer strTok = new StringTokenizer(toSplit, delimiter, false); String[] values = new String[strTok.countTokens()]; int idx = 0; while(strTok.hasMoreTokens()) { values[idx++] = urlDecode(strTok.nextToken()); } return values; } private static String urlDecode(String s) { try { return URLDecoder.decode(s, "utf-8"); } catch (UnsupportedEncodingException e) { // this will never happen since the character encoding name is pre-verified AssertionError ae = new AssertionError("never happens"); ae.initCause(e); throw ae; } } private static class PartialMutation { private ByteBuffer oldValue; // key is a String[] hierarchical path , value is a String private final List relativePaths; PartialMutation() { this.relativePaths = new ArrayList(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy