Package com.ebuddy.cassandra

Source Code of com.ebuddy.cassandra.NestedBatchMutation$PartialMutation

/*
* 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.
* <p/>
* Similar to the Hector BatchMutation, but this implementation also supports a delta
* insertion to an embedded Map object, to support hierarchically nested properties.
* <p/>
* A NestedBatchMutation is also limited to a single row and single column family.
*
* @author Eric Zoerner <a href="mailto:ezoerner@ebuddy.com">ezoerner@ebuddy.com</a>
* @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<String, ByteBuffer> mutations = new HashMap<String, ByteBuffer>();

    /** map of partial mutations, keyed by column name. */
    private final Map<String, PartialMutation> partials = new HashMap<String, PartialMutation>();
    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<ByteBuffer, Map<String, List<Mutation>>> 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<String, PartialMutation> 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<Column> 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<String, PartialMutation> partialKeyValue : partials.entrySet()) {
            PartialMutation partialMutation = partialKeyValue.getValue();

            PropertyValue<?> oldValuePropertyValue = null;
            ByteBuffer oldValueBytes = partialMutation.oldValue;
            if (oldValueBytes != null) {
                 oldValuePropertyValue = PropertyValueFactory.get().createPropertyValue(oldValueBytes);
            }

            Map<String, PropertyValue<?>> 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<String, PropertyValue<?>>();
            } else {
                @SuppressWarnings({"unchecked"})
                PropertyValue<Map<String, PropertyValue<?>>> nestedProps =
                        (PropertyValue<Map<String, 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<KeyValue> relativePaths, Map<String, PropertyValue<?>> topMap) {
        for (KeyValue relativePathKeyValue : relativePaths) {
            applyPathToNestedMap(relativePathKeyValue, topMap);
        }
        return PropertyValueFactory.get().createPropertyValue(topMap).toBytes();
    }

    // modified topMap in place
    private void applyPathToNestedMap(KeyValue relativePathKeyValue, Map<String, PropertyValue<?>> topMap) {
        String[] parts = (String[])relativePathKeyValue.getKey();
        Map<String, PropertyValue<?>> 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<Map<String, PropertyValue<?>>> nextNestedPropertyValue;
                if (nextValue == null || !nextValue.isNestedProperties()) {
                    // unable to drill down here, so overwrite whatever was there with new map
                    Map<String, PropertyValue<?>> newMap = new HashMap<String, PropertyValue<?>>();
                    nextNestedPropertyValue = PropertyValueFactory.get().createPropertyValue(newMap);
                    thisMap.put(part, nextNestedPropertyValue);
                } else {
                    //noinspection unchecked
                    nextNestedPropertyValue = (PropertyValue<Map<String, 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<ByteBuffer, Map<String, List<Mutation>>> createMutationMapIgnoringPartials() {
        assert partials.isEmpty() || partialsApplied;
        Map<ByteBuffer, Map<String, List<Mutation>>> result = new HashMap<ByteBuffer, Map<String, List<Mutation>>>();

        Map<String, List<Mutation>> innerMutationMap = new HashMap<String, List<Mutation>>();
       
        List<Mutation> mutationList = new ArrayList<Mutation>(mutations.size());
        long timestamp = cassandraTemplate.createTimestamp();
        if (superColumnName != null) {
            List<Column> columns = new ArrayList<Column>();
            Mutation mutation = new Mutation();

            for (Map.Entry<String, ByteBuffer> 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<String, ByteBuffer> 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<KeyValue> relativePaths;

        PartialMutation() {
            this.relativePaths = new ArrayList<KeyValue>();
        }
    }

}
TOP

Related Classes of com.ebuddy.cassandra.NestedBatchMutation$PartialMutation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.