org.apache.accumulo.core.dataImpl.KeyExtent.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.accumulo.core.dataImpl.KeyExtent.java

Source

/*
 * 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.dataImpl;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;

import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.dataImpl.thrift.TKeyExtent;
import org.apache.accumulo.core.metadata.MetadataTable;
import org.apache.accumulo.core.metadata.RootTable;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
import org.apache.accumulo.core.util.ByteBufferUtil;
import org.apache.accumulo.core.util.TextUtil;
import org.apache.hadoop.io.BinaryComparable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;

/**
 * keeps track of information needed to identify a tablet
 */
public class KeyExtent implements WritableComparable<KeyExtent> {

    private TableId tableId;
    private Text textEndRow;
    private Text textPrevEndRow;

    private static final TableId EMPTY_ID = TableId.of("");
    private static final Text EMPTY_TEXT = new Text("");

    private void check() {

        if (getTableId() == null)
            throw new IllegalArgumentException("null table id not allowed");

        if (getEndRow() == null || getPrevEndRow() == null)
            return;

        if (getPrevEndRow().compareTo(getEndRow()) >= 0) {
            throw new IllegalArgumentException(
                    "prevEndRow (" + getPrevEndRow() + ") >= endRow (" + getEndRow() + ")");
        }
    }

    /**
     * Default constructor
     *
     */
    public KeyExtent() {
        this.setTableId(EMPTY_ID);
        this.setEndRow(new Text(), false, false);
        this.setPrevEndRow(new Text(), false, false);
    }

    public KeyExtent(TableId table, Text endRow, Text prevEndRow) {
        this.setTableId(table);
        this.setEndRow(endRow, false, true);
        this.setPrevEndRow(prevEndRow, false, true);

        check();
    }

    public KeyExtent(KeyExtent extent) {
        // extent has already deduped table id, so there is no need to do it again
        this.tableId = extent.tableId;
        this.setEndRow(extent.getEndRow(), false, true);
        this.setPrevEndRow(extent.getPrevEndRow(), false, true);

        check();
    }

    public KeyExtent(TKeyExtent tke) {
        this.setTableId(TableId.of(new String(ByteBufferUtil.toBytes(tke.table), UTF_8)));
        this.setEndRow(tke.endRow == null ? null : new Text(ByteBufferUtil.toBytes(tke.endRow)), false, false);
        this.setPrevEndRow(tke.prevEndRow == null ? null : new Text(ByteBufferUtil.toBytes(tke.prevEndRow)), false,
                false);

        check();
    }

    /**
     * Returns a String representing this extent's entry in the Metadata table
     *
     */
    public Text getMetadataEntry() {
        return TabletsSection.getRow(getTableId(), getEndRow());
    }

    // constructor for loading extents from metadata rows
    public KeyExtent(Text flattenedExtent, Value prevEndRow) {
        decodeMetadataRow(flattenedExtent);

        // decode the prev row
        this.setPrevEndRow(decodePrevEndRow(prevEndRow), false, true);

        check();
    }

    // recreates an encoded extent from a string representation
    // this encoding is what is stored as the row id of the metadata table
    public KeyExtent(Text flattenedExtent, Text prevEndRow) {

        decodeMetadataRow(flattenedExtent);

        this.setPrevEndRow(null, false, false);
        if (prevEndRow != null)
            this.setPrevEndRow(prevEndRow, false, true);

        check();
    }

    /**
     * Sets the extents table id
     *
     */
    public void setTableId(TableId tId) {
        Objects.requireNonNull(tId, "null table id not allowed");

        this.tableId = tId;

        hashCode = 0;
    }

    /**
     * Returns the extent's table id
     *
     */
    public TableId getTableId() {
        return tableId;
    }

    private void setEndRow(Text endRow, boolean check, boolean copy) {
        if (endRow != null)
            if (copy)
                this.textEndRow = new Text(endRow);
            else
                this.textEndRow = endRow;
        else
            this.textEndRow = null;

        hashCode = 0;
        if (check)
            check();
    }

    /**
     * Sets this extent's end row
     *
     */
    public void setEndRow(Text endRow) {
        setEndRow(endRow, true, true);
    }

    /**
     * Returns this extent's end row
     *
     */
    public Text getEndRow() {
        return textEndRow;
    }

    /**
     * Return the previous extent's end row
     *
     */
    public Text getPrevEndRow() {
        return textPrevEndRow;
    }

    private void setPrevEndRow(Text prevEndRow, boolean check, boolean copy) {
        if (prevEndRow != null)
            if (copy)
                this.textPrevEndRow = new Text(prevEndRow);
            else
                this.textPrevEndRow = prevEndRow;
        else
            this.textPrevEndRow = null;

        hashCode = 0;
        if (check)
            check();
    }

    /**
     * Sets the previous extent's end row
     *
     */
    public void setPrevEndRow(Text prevEndRow) {
        setPrevEndRow(prevEndRow, true, true);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        Text tid = new Text();
        tid.readFields(in);
        setTableId(TableId.of(tid.toString()));
        boolean hasRow = in.readBoolean();
        if (hasRow) {
            Text er = new Text();
            er.readFields(in);
            setEndRow(er, false, false);
        } else {
            setEndRow(null, false, false);
        }
        boolean hasPrevRow = in.readBoolean();
        if (hasPrevRow) {
            Text per = new Text();
            per.readFields(in);
            setPrevEndRow(per, false, true);
        } else {
            setPrevEndRow(null);
        }

        hashCode = 0;
        check();
    }

    @Override
    public void write(DataOutput out) throws IOException {
        new Text(getTableId().canonical()).write(out);
        if (getEndRow() != null) {
            out.writeBoolean(true);
            getEndRow().write(out);
        } else {
            out.writeBoolean(false);
        }
        if (getPrevEndRow() != null) {
            out.writeBoolean(true);
            getPrevEndRow().write(out);
        } else {
            out.writeBoolean(false);
        }
    }

    /**
     * Returns a String representing the previous extent's entry in the Metadata table
     *
     */
    public Mutation getPrevRowUpdateMutation() {
        return getPrevRowUpdateMutation(this);
    }

    public static Text decodePrevEndRow(Value ibw) {
        Text per = null;

        if (ibw.get()[0] != 0) {
            per = new Text();
            per.set(ibw.get(), 1, ibw.get().length - 1);
        }

        return per;
    }

    public static Value encodePrevEndRow(Text per) {
        if (per == null)
            return new Value(new byte[] { 0 });
        byte[] b = new byte[per.getLength() + 1];
        b[0] = 1;
        System.arraycopy(per.getBytes(), 0, b, 1, per.getLength());
        return new Value(b);
    }

    public static Mutation getPrevRowUpdateMutation(KeyExtent ke) {
        Mutation m = new Mutation(ke.getMetadataEntry());
        TabletsSection.TabletColumnFamily.PREV_ROW_COLUMN.put(m, encodePrevEndRow(ke.getPrevEndRow()));
        return m;
    }

    // The last tablet in a table has no end row, so null sorts last for end row; similarly, the first
    // tablet has no previous end row, so null sorts first for previous end row
    private static final Comparator<KeyExtent> COMPARATOR = Comparator.comparing(KeyExtent::getTableId)
            .thenComparing(KeyExtent::getEndRow, Comparator.nullsLast(Text::compareTo))
            .thenComparing(KeyExtent::getPrevEndRow, Comparator.nullsFirst(Text::compareTo));

    @Override
    public int compareTo(KeyExtent other) {
        return COMPARATOR.compare(this, other);
    }

    private int hashCode = 0;

    @Override
    public int hashCode() {
        if (hashCode != 0)
            return hashCode;

        int prevEndRowHash = 0;
        int endRowHash = 0;
        if (this.getEndRow() != null) {
            endRowHash = this.getEndRow().hashCode();
        }

        if (this.getPrevEndRow() != null) {
            prevEndRowHash = this.getPrevEndRow().hashCode();
        }

        hashCode = getTableId().hashCode() + endRowHash + prevEndRowHash;
        return hashCode;
    }

    private boolean equals(Text t1, Text t2) {
        if (t1 == null || t2 == null)
            return t1 == t2;

        return t1.equals(t2);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof KeyExtent))
            return false;
        KeyExtent oke = (KeyExtent) o;
        return tableId.equals(oke.tableId) && equals(textEndRow, oke.textEndRow)
                && equals(textPrevEndRow, oke.textPrevEndRow);
    }

    @Override
    public String toString() {
        String endRowString;
        String prevEndRowString;
        String tableIdString = getTableId().canonical().replaceAll(";", "\\\\;").replaceAll("\\\\", "\\\\\\\\");

        if (getEndRow() == null)
            endRowString = "<";
        else
            endRowString = ";" + TextUtil.truncate(getEndRow()).toString().replaceAll(";", "\\\\;")
                    .replaceAll("\\\\", "\\\\\\\\");

        if (getPrevEndRow() == null)
            prevEndRowString = "<";
        else
            prevEndRowString = ";" + TextUtil.truncate(getPrevEndRow()).toString().replaceAll(";", "\\\\;")
                    .replaceAll("\\\\", "\\\\\\\\");

        return tableIdString + endRowString + prevEndRowString;
    }

    public UUID getUUID() {
        try {

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);

            // to get a unique hash it is important to encode the data
            // like it is being serialized

            this.write(dos);

            dos.close();

            return UUID.nameUUIDFromBytes(baos.toByteArray());

        } catch (IOException e) {
            // should not happen since we are writing to memory
            throw new RuntimeException(e);
        }
    }

    // note: this is only the encoding of the table id and the last row, not the prev row
    /**
     * Populates the extent's fields based on a flatted extent
     *
     */
    private void decodeMetadataRow(Text flattenedExtent) {
        int semiPos = -1;
        int ltPos = -1;

        for (int i = 0; i < flattenedExtent.getLength(); i++) {
            if (flattenedExtent.getBytes()[i] == ';' && semiPos < 0) {
                // want the position of the first semicolon
                semiPos = i;
            }

            if (flattenedExtent.getBytes()[i] == '<') {
                ltPos = i;
            }
        }

        if (semiPos < 0 && ltPos < 0) {
            throw new IllegalArgumentException("Metadata row does not contain ; or <  " + flattenedExtent);
        }

        if (semiPos < 0) {

            if (ltPos != flattenedExtent.getLength() - 1) {
                throw new IllegalArgumentException("< must come at end of Metadata row  " + flattenedExtent);
            }

            String decodedString = new String(
                    Arrays.copyOfRange(flattenedExtent.getBytes(), 0, flattenedExtent.getLength() - 1), UTF_8);
            TableId tableId = TableId.of(decodedString);
            this.setTableId(tableId);
            this.setEndRow(null, false, false);
        } else {

            TableId tableId = TableId
                    .of(new String(Arrays.copyOfRange(flattenedExtent.getBytes(), 0, semiPos), UTF_8));

            Text endRow = new Text();
            endRow.set(flattenedExtent.getBytes(), semiPos + 1, flattenedExtent.getLength() - (semiPos + 1));

            this.setTableId(tableId);

            this.setEndRow(endRow, false, false);
        }
    }

    public static byte[] tableOfMetadataRow(Text row) {
        KeyExtent ke = new KeyExtent();
        ke.decodeMetadataRow(row);
        return ke.getTableId().canonical().getBytes(UTF_8);
    }

    public boolean contains(final ByteSequence bsrow) {
        if (bsrow == null) {
            throw new IllegalArgumentException(
                    "Passing null to contains is ambiguous, could be in first or last extent of table");
        }

        BinaryComparable row = new BinaryComparable() {

            @Override
            public int getLength() {
                return bsrow.length();
            }

            @Override
            public byte[] getBytes() {
                if (bsrow.isBackedByArray() && bsrow.offset() == 0)
                    return bsrow.getBackingArray();

                return bsrow.toArray();
            }
        };

        return (this.getPrevEndRow() == null || this.getPrevEndRow().compareTo(row) < 0)
                && (this.getEndRow() == null || this.getEndRow().compareTo(row) >= 0);
    }

    public boolean contains(BinaryComparable row) {
        if (row == null) {
            throw new IllegalArgumentException(
                    "Passing null to contains is ambiguous, could be in first or last extent of table");
        }

        return (this.getPrevEndRow() == null || this.getPrevEndRow().compareTo(row) < 0)
                && (this.getEndRow() == null || this.getEndRow().compareTo(row) >= 0);
    }

    public Range toDataRange() {
        return new Range(getPrevEndRow(), false, getEndRow(), true);
    }

    public Range toMetadataRange() {

        Text metadataPrevRow = TabletsSection.getRow(getTableId(),
                getPrevEndRow() == null ? EMPTY_TEXT : getPrevEndRow());

        return new Range(metadataPrevRow, getPrevEndRow() == null, getMetadataEntry(), true);
    }

    public static SortedSet<KeyExtent> findChildren(KeyExtent ke, SortedSet<KeyExtent> tablets) {

        SortedSet<KeyExtent> children = null;

        for (KeyExtent tabletKe : tablets) {

            if (ke.getPrevEndRow() == tabletKe.getPrevEndRow()
                    || ke.getPrevEndRow() != null && tabletKe.getPrevEndRow() != null
                            && tabletKe.getPrevEndRow().compareTo(ke.getPrevEndRow()) == 0) {
                children = new TreeSet<>();
            }

            if (children != null) {
                children.add(tabletKe);
            }

            if (ke.getEndRow() == tabletKe.getEndRow() || ke.getEndRow() != null && tabletKe.getEndRow() != null
                    && tabletKe.getEndRow().compareTo(ke.getEndRow()) == 0) {
                return children;
            }
        }

        return new TreeSet<>();
    }

    public static KeyExtent findContainingExtent(KeyExtent extent, SortedSet<KeyExtent> extents) {

        KeyExtent lookupExtent = new KeyExtent(extent);
        lookupExtent.setPrevEndRow(null);

        SortedSet<KeyExtent> tailSet = extents.tailSet(lookupExtent);

        if (tailSet.isEmpty()) {
            return null;
        }

        KeyExtent first = tailSet.first();

        if (first.getTableId().compareTo(extent.getTableId()) != 0) {
            return null;
        }

        if (first.getPrevEndRow() == null) {
            return first;
        }

        if (extent.getPrevEndRow() == null) {
            return null;
        }

        if (extent.getPrevEndRow().compareTo(first.getPrevEndRow()) >= 0)
            return first;
        return null;
    }

    private static boolean startsAfter(KeyExtent nke, KeyExtent ke) {

        int tiCmp = ke.getTableId().compareTo(nke.getTableId());

        if (tiCmp > 0) {
            return true;
        }

        return ke.getPrevEndRow() != null && nke.getEndRow() != null
                && ke.getPrevEndRow().compareTo(nke.getEndRow()) >= 0;
    }

    private static Text rowAfterPrevRow(KeyExtent nke) {
        Text row = new Text(nke.getPrevEndRow());
        row.append(new byte[] { 0 }, 0, 1);
        return row;
    }

    // Some duplication with TabletLocatorImpl
    public static Set<KeyExtent> findOverlapping(KeyExtent nke, SortedSet<KeyExtent> extents) {
        if (nke == null || extents == null || extents.isEmpty())
            return Collections.emptySet();

        SortedSet<KeyExtent> start;

        if (nke.getPrevEndRow() != null) {
            Text row = rowAfterPrevRow(nke);
            KeyExtent lookupKey = new KeyExtent(nke.getTableId(), row, null);
            start = extents.tailSet(lookupKey);
        } else {
            KeyExtent lookupKey = new KeyExtent(nke.getTableId(), new Text(), null);
            start = extents.tailSet(lookupKey);
        }

        TreeSet<KeyExtent> result = new TreeSet<>();
        for (KeyExtent ke : start) {
            if (startsAfter(nke, ke)) {
                break;
            }
            result.add(ke);
        }
        return result;
    }

    public boolean overlaps(KeyExtent other) {
        SortedSet<KeyExtent> set = new TreeSet<>();
        set.add(other);
        return !findOverlapping(this, set).isEmpty();
    }

    // Specialization of findOverlapping(KeyExtent, SortedSet<KeyExtent> to work with SortedMap
    public static Set<KeyExtent> findOverlapping(KeyExtent nke, SortedMap<KeyExtent, ?> extents) {
        if (nke == null || extents == null || extents.isEmpty())
            return Collections.emptySet();

        SortedMap<KeyExtent, ?> start;

        if (nke.getPrevEndRow() != null) {
            Text row = rowAfterPrevRow(nke);
            KeyExtent lookupKey = new KeyExtent(nke.getTableId(), row, null);
            start = extents.tailMap(lookupKey);
        } else {
            KeyExtent lookupKey = new KeyExtent(nke.getTableId(), new Text(), null);
            start = extents.tailMap(lookupKey);
        }

        TreeSet<KeyExtent> result = new TreeSet<>();
        for (Entry<KeyExtent, ?> entry : start.entrySet()) {
            KeyExtent ke = entry.getKey();
            if (startsAfter(nke, ke)) {
                break;
            }
            result.add(ke);
        }
        return result;
    }

    public static Text getMetadataEntry(KeyExtent extent) {
        return TabletsSection.getRow(extent.getTableId(), extent.getEndRow());
    }

    public TKeyExtent toThrift() {
        return new TKeyExtent(ByteBuffer.wrap(tableId.canonical().getBytes(UTF_8)),
                textEndRow == null ? null : TextUtil.getByteBuffer(textEndRow),
                textPrevEndRow == null ? null : TextUtil.getByteBuffer(textPrevEndRow));
    }

    public boolean isPreviousExtent(KeyExtent prevExtent) {
        if (prevExtent == null)
            return getPrevEndRow() == null;

        if (!prevExtent.getTableId().equals(getTableId()))
            throw new IllegalArgumentException("Cannot compare across tables " + prevExtent + " " + this);

        if (prevExtent.getEndRow() == null)
            return false;

        if (getPrevEndRow() == null)
            return false;

        return prevExtent.getEndRow().equals(getPrevEndRow());
    }

    public boolean isMeta() {
        return getTableId().equals(MetadataTable.ID) || isRootTablet();
    }

    public boolean isRootTablet() {
        return getTableId().equals(RootTable.ID);
    }
}