Package org.apache.accumulo.core.iterators.user

Source Code of org.apache.accumulo.core.iterators.user.TransformingIterator

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.accumulo.core.iterators.user;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;

import org.apache.accumulo.core.Constants;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.PartialKey;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.IteratorEnvironment;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.iterators.OptionDescriber;
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.core.iterators.WrappingIterator;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.security.ColumnVisibility;
import org.apache.accumulo.core.security.VisibilityEvaluator;
import org.apache.accumulo.core.security.VisibilityParseException;
import org.apache.accumulo.core.util.BadArgumentException;
import org.apache.accumulo.core.util.Pair;
import org.apache.commons.collections.BufferOverflowException;
import org.apache.commons.collections.map.LRUMap;
import org.apache.hadoop.io.Text;
import org.apache.log4j.Logger;

/**
* The TransformingIterator allows portions of a key (except for the row) to be transformed. This iterator handles the details that come with modifying keys
* (i.e., that the sort order could change). In order to do so, however, the iterator must put all keys sharing the same prefix in memory. Prefix is defined as
* the parts of the key that are not modified by this iterator. That is, if the iterator modifies column qualifier and timestamp, then the prefix is row and
* column family. In that case, the iterator must load all column qualifiers for each row/column family pair into memory. Given this constraint, care must be
* taken by users of this iterator to ensure it is not run in such a way that will overrun memory in a tablet server.
* <p>
* If the implementing iterator is transforming column families, then it must also override {@code untransformColumnFamilies(Collection)} to handle the case
* when column families are fetched at scan time. The fetched column families will/must be in the transformed space, and the untransformed column families need
* to be passed to this iterator's source. If it is not possible to write a reverse transformation (e.g., the column family transformation depends on the row
* value or something like that), then the iterator must not fetch specific column families (or only fetch column families that are known to not transform at
* all).
* <p>
* If the implementing iterator is transforming column visibilities, then users must be careful NOT to fetch column qualifiers from the scanner. The reason for
* this is due to ACCUMULO-??? (insert issue number).
* <p>
* If the implementing iterator is transforming column visibilities, then the user should be sure to supply authorizations via the {@link #AUTH_OPT} iterator
* option (note that this is only necessary for scan scope iterators). The supplied authorizations should be in the transformed space, but the authorizations
* supplied to the scanner should be in the untransformed space. That is, if the iterator transforms A to 1, B to 2, C to 3, etc, then the auths supplied when
* the scanner is constructed should be A,B,C,... and the auths supplied to the iterator should be 1,2,3,... The reason for this is that the scanner performs
* security filtering before this iterator is called, so the authorizations need to be in the original untransformed space. Since the iterator can transform
* visibilities, it is possible that it could produce visibilities that the user cannot see, so the transformed keys must be tested to ensure the user is
* allowed to view them. Note that this test is not necessary when the iterator is not used in the scan scope since no security filtering is performed during
* major and minor compactions. It should also be noted that this iterator implements the security filtering rather than relying on a follow-on iterator to do
* it so that we ensure the test is performed.
*/
abstract public class TransformingIterator extends WrappingIterator implements OptionDescriber {
  public static final String AUTH_OPT = "authorizations";
  public static final String MAX_BUFFER_SIZE_OPT = "maxBufferSize";
  private static final long DEFAULT_MAX_BUFFER_SIZE = 10000000;

  protected Logger log = Logger.getLogger(getClass());

  protected ArrayList<Pair<Key,Value>> keys = new ArrayList<Pair<Key,Value>>();
  protected int keyPos = -1;
  protected boolean scanning;
  protected Range seekRange;
  protected Collection<ByteSequence> seekColumnFamilies;
  protected boolean seekColumnFamiliesInclusive;

  private VisibilityEvaluator ve = null;
  private LRUMap visibleCache = null;
  private LRUMap parsedVisibilitiesCache = null;
  private long maxBufferSize;

  private static Comparator<Pair<Key,Value>> keyComparator = new Comparator<Pair<Key,Value>>() {
    @Override
    public int compare(Pair<Key,Value> o1, Pair<Key,Value> o2) {
      return o1.getFirst().compareTo(o2.getFirst());
    }
  };

  public TransformingIterator() {}

  @Override
  public void init(SortedKeyValueIterator<Key,Value> source, Map<String,String> options, IteratorEnvironment env) throws IOException {
    super.init(source, options, env);
    scanning = IteratorScope.scan.equals(env.getIteratorScope());
    if (scanning) {
      String auths = options.get(AUTH_OPT);
      if (auths != null && !auths.isEmpty()) {
        ve = new VisibilityEvaluator(new Authorizations(auths.getBytes(Constants.UTF8)));
        visibleCache = new LRUMap(100);
      }
    }

    if (options.containsKey(MAX_BUFFER_SIZE_OPT)) {
      maxBufferSize = AccumuloConfiguration.getMemoryInBytes(options.get(MAX_BUFFER_SIZE_OPT));
    } else {
      maxBufferSize = DEFAULT_MAX_BUFFER_SIZE;
    }

    parsedVisibilitiesCache = new LRUMap(100);
  }

  @Override
  public IteratorOptions describeOptions() {
    String desc = "This iterator allows ranges of key to be transformed (with the exception of row transformations).";
    String authDesc = "Comma-separated list of user's scan authorizations.  "
        + "If excluded or empty, then no visibility check is performed on transformed keys.";
    String bufferDesc = "Maximum buffer size (in accumulo memory spec) to use for buffering keys before throwing a BufferOverflowException.  "
        + "Users should keep this limit in mind when deciding what to transform.  That is, if transforming the column family for example, then all "
        + "keys sharing the same row and column family must fit within this limit (along with their associated values)";
    HashMap<String,String> namedOptions = new HashMap<String,String>();
    namedOptions.put(AUTH_OPT, authDesc);
    namedOptions.put(MAX_BUFFER_SIZE_OPT, bufferDesc);
    return new IteratorOptions(getClass().getSimpleName(), desc, namedOptions, null);
  }

  @Override
  public boolean validateOptions(Map<String,String> options) {

    for (Entry<String,String> option : options.entrySet()) {
      try {
        if (option.getKey().equals(AUTH_OPT)) {
          new Authorizations(option.getValue().getBytes(Constants.UTF8));
        } else if (option.getKey().equals(MAX_BUFFER_SIZE_OPT)) {
          AccumuloConfiguration.getMemoryInBytes(option.getValue());
        }
      } catch (Exception e) {
        throw new IllegalArgumentException("Failed to parse opt " + option.getKey() + " " + option.getValue(), e);
      }
    }

    return true;
  }

  @Override
  public SortedKeyValueIterator<Key,Value> deepCopy(IteratorEnvironment env) {
    TransformingIterator copy;

    try {
      copy = getClass().newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    copy.setSource(getSource().deepCopy(env));

    copy.scanning = scanning;
    copy.keyPos = keyPos;
    copy.keys.addAll(keys);
    copy.seekRange = (seekRange == null) ? null : new Range(seekRange);
    copy.seekColumnFamilies = (seekColumnFamilies == null) ? null : new HashSet<ByteSequence>(seekColumnFamilies);
    copy.seekColumnFamiliesInclusive = seekColumnFamiliesInclusive;

    copy.ve = ve;
    if (visibleCache != null) {
      copy.visibleCache = new LRUMap(visibleCache.maxSize());
      copy.visibleCache.putAll(visibleCache);
    }

    if (parsedVisibilitiesCache != null) {
      copy.parsedVisibilitiesCache = new LRUMap(parsedVisibilitiesCache.maxSize());
      copy.parsedVisibilitiesCache.putAll(parsedVisibilitiesCache);
    }

    copy.maxBufferSize = maxBufferSize;

    return copy;
  }

  @Override
  public boolean hasTop() {
    return keyPos >= 0 && keyPos < keys.size();
  }

  @Override
  public Key getTopKey() {
    return hasTop() ? keys.get(keyPos).getFirst() : null;
  }

  @Override
  public Value getTopValue() {
    return hasTop() ? keys.get(keyPos).getSecond() : null;
  }

  @Override
  public void next() throws IOException {
    // Move on to the next entry since we returned the entry at keyPos before
    if (keyPos >= 0)
      keyPos++;

    // If we emptied out the transformed key map then transform the next key
    // set from the source. It’s possible that transformation could produce keys
    // that are outside of our range or are not visible to the end user, so after the
    // call below we might not have added any keys to the map. Keep going until
    // we either get some keys in the map or exhaust the source iterator.
    while (!hasTop() && super.hasTop())
      transformKeys();
  }

  @Override
  public void seek(Range range, Collection<ByteSequence> columnFamilies, boolean inclusive) throws IOException {
    seekRange = new Range(range);
    seekColumnFamilies = columnFamilies;
    seekColumnFamiliesInclusive = inclusive;

    // Seek the source iterator, but use a recalculated range that ensures
    // we see all keys with the same "prefix." We need to do this since
    // transforming could change the sort order and transformed keys that
    // are before the range start could be inside the range after transformation.
    super.seek(computeReseekRange(range), untransformColumnFamilies(columnFamilies), inclusive);

    // Range clipping could cause us to trim out all the keys we transformed.
    // Keep looping until we either have some keys in the output range, or have
    // exhausted the source iterator.
    keyPos = -1; // “Clear” list so hasTop returns false to get us into the loop (transformKeys actually clears)
    while (!hasTop() && super.hasTop()) {
      // Build up a sorted list of all keys for the same prefix. When
      // people ask for keys, return from this list first until it is empty
      // before incrementing the source iterator.
      transformKeys();
    }
  }

  private static class RangeIterator implements SortedKeyValueIterator<Key,Value> {

    private SortedKeyValueIterator<Key,Value> source;
    private Key prefixKey;
    private PartialKey keyPrefix;
    private boolean hasTop = false;

    RangeIterator(SortedKeyValueIterator<Key,Value> source, Key prefixKey, PartialKey keyPrefix) {
      this.source = source;
      this.prefixKey = prefixKey;
      this.keyPrefix = keyPrefix;
    }

    @Override
    public void init(SortedKeyValueIterator<Key,Value> source, Map<String,String> options, IteratorEnvironment env) throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean hasTop() {
      // only have a top if the prefix matches
      return hasTop = source.hasTop() && source.getTopKey().equals(prefixKey, keyPrefix);
    }

    @Override
    public void next() throws IOException {
      // do not let user advance too far and try to avoid reexecuting hasTop()
      if (!hasTop && !hasTop())
        throw new NoSuchElementException();
      hasTop = false;
      source.next();
    }

    @Override
    public void seek(Range range, Collection<ByteSequence> columnFamilies, boolean inclusive) throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public Key getTopKey() {
      return source.getTopKey();
    }

    @Override
    public Value getTopValue() {
      return source.getTopValue();
    }

    @Override
    public SortedKeyValueIterator<Key,Value> deepCopy(IteratorEnvironment env) {
      throw new UnsupportedOperationException();
    }

  }

  /**
   * Reads all keys matching the first key's prefix from the source iterator, transforms them, and sorts the resulting keys. Transformed keys that fall outside
   * of our seek range or can't be seen by the user are excluded.
   */
  protected void transformKeys() throws IOException {
    keyPos = -1;
    keys.clear();
    final Key prefixKey = super.hasTop() ? new Key(super.getTopKey()) : null;

    transformRange(new RangeIterator(getSource(), prefixKey, getKeyPrefix()), new KVBuffer() {

      long appened = 0;

      @Override
      public void append(Key key, Value val) {
        // ensure the key provided by the user has the correct prefix
        if (!key.equals(prefixKey, getKeyPrefix()))
          throw new IllegalArgumentException("Key prefixes are not equal " + key + " " + prefixKey);

        // Transformation could have produced a key that falls outside
        // of the seek range, or one that the user cannot see. Check
        // these before adding it to the output list.
        if (includeTransformedKey(key)) {

          // try to defend against a scan or compaction using all memory in a tablet server
          if (appened > maxBufferSize)
            throw new BufferOverflowException("Exceeded buffer size of " + maxBufferSize + ", prefixKey: " + prefixKey);

          if (getSource().hasTop() && key == getSource().getTopKey())
            key = new Key(key);
          keys.add(new Pair<Key,Value>(key, new Value(val)));
          appened += (key.getSize() + val.getSize() + 128);
        }
      }
    });

    // consume any key in range that user did not consume
    while (super.hasTop() && super.getTopKey().equals(prefixKey, getKeyPrefix())) {
      super.next();
    }

    if (!keys.isEmpty()) {
      Collections.sort(keys, keyComparator);
      keyPos = 0;
    }
  }

  /**
   * Determines whether or not to include {@code transformedKey} in the output. It is possible that transformation could have produced a key that falls outside
   * of the seek range, a key with a visibility the user can't see, a key with a visibility that doesn't parse, or a key with a column family that wasn't
   * fetched. We only do some checks (outside the range, user can see) if we're scanning. The range check is not done for major/minor compaction since seek
   * ranges won't be in our transformed key space and we will never change the row so we can't produce keys that would fall outside the tablet anyway.
   *
   * @param transformedKey
   *          the key to check
   * @return {@code true} if the key should be included and {@code false} if not
   */
  protected boolean includeTransformedKey(Key transformedKey) {
    boolean include = canSee(transformedKey);
    if (scanning && seekRange != null) {
      include = include && seekRange.contains(transformedKey);
    }
    return include;
  }

  /**
   * Indicates whether or not the user is able to see {@code key}. If the user has not supplied authorizations, or the iterator is not in the scan scope, then
   * this method simply returns {@code true}. Otherwise, {@code key}'s column visibility is tested against the user-supplied authorizations, and the test result
   * is returned. For performance, the test results are cached so that the same visibility is not tested multiple times.
   *
   * @param key
   *          the key to test
   * @return {@code true} if the key is visible or iterator is not scanning, and {@code false} if not
   */
  protected boolean canSee(Key key) {
    // Ensure that the visibility (which could have been transformed) parses. Must always do this check, even if visibility is not evaluated.
    ByteSequence visibility = key.getColumnVisibilityData();
    ColumnVisibility colVis = null;
    Boolean parsed = (Boolean) parsedVisibilitiesCache.get(visibility);
    if (parsed == null) {
      try {
        colVis = new ColumnVisibility(visibility.toArray());
        parsedVisibilitiesCache.put(visibility, Boolean.TRUE);
      } catch (BadArgumentException e) {
        log.error("Parse error after transformation : " + visibility);
        parsedVisibilitiesCache.put(visibility, Boolean.FALSE);
        if (scanning) {
          return false;
        } else {
          throw e;
        }
      }
    } else if (!parsed) {
      if (scanning)
        return false;
      else
        throw new IllegalStateException();
    }

    Boolean visible = canSeeColumnFamily(key);

    if (!scanning || !visible || ve == null || visibleCache == null || visibility.length() == 0)
      return visible;

    visible = (Boolean) visibleCache.get(visibility);
    if (visible == null) {
      try {
        if (colVis == null)
          colVis = new ColumnVisibility(visibility.toArray());
        visible = ve.evaluate(colVis);
        visibleCache.put(visibility, visible);
      } catch (VisibilityParseException e) {
        log.error("Parse Error", e);
        visible = Boolean.FALSE;
      } catch (BadArgumentException e) {
        log.error("Parse Error", e);
        visible = Boolean.FALSE;
      }
    }

    return visible;
  }

  /**
   * Indicates whether or not {@code key} can be seen, according to the fetched column families for this iterator.
   *
   * @param key
   *          the key whose column family is to be tested
   * @return {@code true} if {@code key}'s column family is one of those fetched in the set passed to our {@link #seek(Range, Collection, boolean)} method
   */
  protected boolean canSeeColumnFamily(Key key) {
    boolean visible = true;
    if (seekColumnFamilies != null) {
      ByteSequence columnFamily = key.getColumnFamilyData();
      if (seekColumnFamiliesInclusive)
        visible = seekColumnFamilies.contains(columnFamily);
      else
        visible = !seekColumnFamilies.contains(columnFamily);
    }
    return visible;
  }

  /**
   * Possibly expand {@code range} to include everything for the key prefix we are working with. That is, if our prefix is ROW_COLFAM, then we need to expand
   * the range so we're sure to include all entries having the same row and column family as the start/end of the range.
   *
   * @param range
   *          the range to expand
   * @return the modified range
   */
  protected Range computeReseekRange(Range range) {
    Key startKey = range.getStartKey();
    boolean startKeyInclusive = range.isStartKeyInclusive();
    // If anything after the prefix is set, then clip the key so we include
    // everything for the prefix.
    if (isSetAfterPart(startKey, getKeyPrefix())) {
      startKey = copyPartialKey(startKey, getKeyPrefix());
      startKeyInclusive = true;
    }
    Key endKey = range.getEndKey();
    boolean endKeyInclusive = range.isEndKeyInclusive();
    if (isSetAfterPart(endKey, getKeyPrefix())) {
      endKey = endKey.followingKey(getKeyPrefix());
      endKeyInclusive = true;
    }
    return new Range(startKey, startKeyInclusive, endKey, endKeyInclusive);
  }

  /**
   * Indicates whether or not any part of {@code key} excluding {@code part} is set. For example, if part is ROW_COLFAM_COLQUAL, then this method determines
   * whether or not the column visibility, timestamp, or delete flag is set on {@code key}.
   *
   * @param key
   *          the key to check
   * @param part
   *          the part of the key that doesn't need to be checked (everything after does)
   * @return {@code true} if anything after {@code part} is set on {@code key}, and {@code false} if not
   */
  protected boolean isSetAfterPart(Key key, PartialKey part) {
    boolean isSet = false;
    if (key != null) {
      // Breaks excluded on purpose.
      switch (part) {
        case ROW:
          isSet = isSet || key.getColumnFamilyData().length() > 0;
        case ROW_COLFAM:
          isSet = isSet || key.getColumnQualifierData().length() > 0;
        case ROW_COLFAM_COLQUAL:
          isSet = isSet || key.getColumnVisibilityData().length() > 0;
        case ROW_COLFAM_COLQUAL_COLVIS:
          isSet = isSet || key.getTimestamp() < Long.MAX_VALUE;
        case ROW_COLFAM_COLQUAL_COLVIS_TIME:
          isSet = isSet || key.isDeleted();
        case ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL:
          break;
      }
    }
    return isSet;
  }

  /**
   * Creates a copy of {@code key}, copying only the parts of the key specified in {@code part}. For example, if {@code part} is ROW_COLFAM_COLQUAL, then this
   * method would copy the row, column family, and column qualifier from {@code key} into a new key.
   *
   * @param key
   *          the key to copy
   * @param part
   *          the parts of {@code key} to copy
   * @return the new key containing {@code part} of {@code key}
   */
  protected Key copyPartialKey(Key key, PartialKey part) {
    Key keyCopy;
    switch (part) {
      case ROW:
        keyCopy = new Key(key.getRow());
        break;
      case ROW_COLFAM:
        keyCopy = new Key(key.getRow(), key.getColumnFamily());
        break;
      case ROW_COLFAM_COLQUAL:
        keyCopy = new Key(key.getRow(), key.getColumnFamily(), key.getColumnQualifier());
        break;
      case ROW_COLFAM_COLQUAL_COLVIS:
        keyCopy = new Key(key.getRow(), key.getColumnFamily(), key.getColumnQualifier(), key.getColumnVisibility());
        break;
      case ROW_COLFAM_COLQUAL_COLVIS_TIME:
        keyCopy = new Key(key.getRow(), key.getColumnFamily(), key.getColumnQualifier(), key.getColumnVisibility(), key.getTimestamp());
        break;
      default:
        throw new IllegalArgumentException("Unsupported key part: " + part);
    }
    return keyCopy;
  }

  /**
   * Make a new key with all parts (including delete flag) coming from {@code originalKey} but use {@code newColFam} as the column family.
   */
  protected Key replaceColumnFamily(Key originalKey, Text newColFam) {
    byte[] row = originalKey.getRowData().toArray();
    byte[] cf = newColFam.getBytes();
    byte[] cq = originalKey.getColumnQualifierData().toArray();
    byte[] cv = originalKey.getColumnVisibilityData().toArray();
    long timestamp = originalKey.getTimestamp();
    Key newKey = new Key(row, 0, row.length, cf, 0, newColFam.getLength(), cq, 0, cq.length, cv, 0, cv.length, timestamp);
    newKey.setDeleted(originalKey.isDeleted());
    return newKey;
  }

  /**
   * Make a new key with all parts (including delete flag) coming from {@code originalKey} but use {@code newColQual} as the column qualifier.
   */
  protected Key replaceColumnQualifier(Key originalKey, Text newColQual) {
    byte[] row = originalKey.getRowData().toArray();
    byte[] cf = originalKey.getColumnFamilyData().toArray();
    byte[] cq = newColQual.getBytes();
    byte[] cv = originalKey.getColumnVisibilityData().toArray();
    long timestamp = originalKey.getTimestamp();
    Key newKey = new Key(row, 0, row.length, cf, 0, cf.length, cq, 0, newColQual.getLength(), cv, 0, cv.length, timestamp);
    newKey.setDeleted(originalKey.isDeleted());
    return newKey;
  }

  /**
   * Make a new key with all parts (including delete flag) coming from {@code originalKey} but use {@code newColVis} as the column visibility.
   */
  protected Key replaceColumnVisibility(Key originalKey, Text newColVis) {
    byte[] row = originalKey.getRowData().toArray();
    byte[] cf = originalKey.getColumnFamilyData().toArray();
    byte[] cq = originalKey.getColumnQualifierData().toArray();
    byte[] cv = newColVis.getBytes();
    long timestamp = originalKey.getTimestamp();
    Key newKey = new Key(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, cv, 0, newColVis.getLength(), timestamp);
    newKey.setDeleted(originalKey.isDeleted());
    return newKey;
  }

  /**
   * Make a new key with a column family, column qualifier, and column visibility. Copy the rest of the parts of the key (including delete flag) from
   * {@code originalKey}.
   */
  protected Key replaceKeyParts(Key originalKey, Text newColFam, Text newColQual, Text newColVis) {
    byte[] row = originalKey.getRowData().toArray();
    byte[] cf = newColFam.getBytes();
    byte[] cq = newColQual.getBytes();
    byte[] cv = newColVis.getBytes();
    long timestamp = originalKey.getTimestamp();
    Key newKey = new Key(row, 0, row.length, cf, 0, newColFam.getLength(), cq, 0, newColQual.getLength(), cv, 0, newColVis.getLength(), timestamp);
    newKey.setDeleted(originalKey.isDeleted());
    return newKey;
  }

  /**
   * Make a new key with a column qualifier, and column visibility. Copy the rest of the parts of the key (including delete flag) from {@code originalKey}.
   */
  protected Key replaceKeyParts(Key originalKey, Text newColQual, Text newColVis) {
    byte[] row = originalKey.getRowData().toArray();
    byte[] cf = originalKey.getColumnFamilyData().toArray();
    byte[] cq = newColQual.getBytes();
    byte[] cv = newColVis.getBytes();
    long timestamp = originalKey.getTimestamp();
    Key newKey = new Key(row, 0, row.length, cf, 0, cf.length, cq, 0, newColQual.getLength(), cv, 0, newColVis.getLength(), timestamp);
    newKey.setDeleted(originalKey.isDeleted());
    return newKey;
  }

  /**
   * Reverses the transformation applied to column families that are fetched at seek time. If this iterator is transforming column families, then this method
   * should be overridden to reverse the transformation on the supplied collection of column families. This is necessary since the fetch/seek will be performed
   * in the transformed space, but when passing the column family set on to the source, the column families need to be in the untransformed space.
   *
   * @param columnFamilies
   *          the column families that have been fetched at seek time
   * @return the untransformed column families that would transform info {@code columnFamilies}
   */
  protected Collection<ByteSequence> untransformColumnFamilies(Collection<ByteSequence> columnFamilies) {
    return columnFamilies;
  }

  /**
   * Indicates the prefix of keys that will be transformed by this iterator. In other words, this is the part of the key that will <i>not</i> be transformed by
   * this iterator. For example, if this method returns ROW_COLFAM, then {@link #transformKeys()} may be changing the column qualifier, column visibility, or
   * timestamp, but it won't be changing the row or column family.
   *
   * @return the part of the key this iterator is not transforming
   */
  abstract protected PartialKey getKeyPrefix();
 
  public interface KVBuffer {
    void append(Key key, Value val);
  }

  /**
   * Transforms {@code input}. This method must not change the row part of the key, and must only change the parts of the key after the return value of
   * {@link #getKeyPrefix()}. Implementors must also remember to copy the delete flag from {@code originalKey} onto the new key. Or, implementors should use one
   * of the helper methods to produce the new key. See any of the replaceKeyParts methods.
   *
   * @param input
   *          An iterator over a group of keys with the same prefix. This iterator provides an efficient view, bounded by the prefix, of the underlying iterator
   *          and can not be seeked.
   * @param output
   *          An output buffer that holds transformed key values. All key values added to the buffer must have the same prefix as the input keys.
   * @see #replaceColumnFamily(Key, Text)
   * @see #replaceColumnQualifier(Key, Text)
   * @see #replaceColumnVisibility(Key, Text)
   * @see #replaceKeyParts(Key, Text, Text)
   * @see #replaceKeyParts(Key, Text, Text, Text)
   */
  abstract protected void transformRange(SortedKeyValueIterator<Key,Value> input, KVBuffer output) throws IOException;

  /**
   * Configure authorizations used for post transformation filtering.
   *
   */
  public static void setAuthorizations(IteratorSetting config, Authorizations auths) {
    config.addOption(AUTH_OPT, auths.serialize());
  }

  /**
   * Configure the maximum amount of memory that can be used for transformation. If this memory is exceeded an exception will be thrown.
   *
   * @param maxBufferSize
   *          size in bytes
   */
  public static void setMaxBufferSize(IteratorSetting config, long maxBufferSize) {
    config.addOption(MAX_BUFFER_SIZE_OPT, maxBufferSize + "");
  }

}
TOP

Related Classes of org.apache.accumulo.core.iterators.user.TransformingIterator

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.