001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.io;
020
021import java.io.BufferedInputStream;
022import java.io.DataInput;
023import java.io.DataInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.util.Arrays;
027
028import org.apache.yetus.audience.InterfaceAudience;
029import org.apache.hadoop.fs.FSDataOutputStream;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.Path;
032import org.apache.hadoop.hbase.KeyValueUtil;
033import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
034import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
035import org.apache.hadoop.hbase.shaded.protobuf.generated.FSProtos;
036import org.apache.hadoop.hbase.util.Bytes;
037
038/**
039 * A reference to the top or bottom half of a store file where 'bottom' is the first half
040 * of the file containing the keys that sort lowest and 'top' is the second half
041 * of the file with keys that sort greater than those of the bottom half.  The file referenced
042 * lives under a different region.  References are made at region split time.
043 *
044 * <p>References work with a special half store file type.  References know how
045 * to write out the reference format in the file system and are what is juggled
046 * when references are mixed in with direct store files.  The half store file
047 * type is used reading the referred to file.
048 *
049 * <p>References to store files located over in some other region look like
050 * this in the file system
051 * <code>1278437856009925445.3323223323</code>:
052 * i.e. an id followed by hash of the referenced region.
053 * Note, a region is itself not splittable if it has instances of store file
054 * references.  References are cleaned up by compactions.
055 */
056@InterfaceAudience.Private
057public class Reference {
058  private byte [] splitkey;
059  private Range region;
060
061  /**
062   * For split HStoreFiles, it specifies if the file covers the lower half or
063   * the upper half of the key range
064   */
065  static enum Range {
066    /** HStoreFile contains upper half of key range */
067    top,
068    /** HStoreFile contains lower half of key range */
069    bottom
070  }
071
072  /**
073   * @param splitRow
074   * @return A {@link Reference} that points at top half of a an hfile
075   */
076  public static Reference createTopReference(final byte [] splitRow) {
077    return new Reference(splitRow, Range.top);
078  }
079
080  /**
081   * @param splitRow
082   * @return A {@link Reference} that points at the bottom half of a an hfile
083   */
084  public static Reference createBottomReference(final byte [] splitRow) {
085    return new Reference(splitRow, Range.bottom);
086  }
087
088  /**
089   * Constructor
090   * @param splitRow This is row we are splitting around.
091   * @param fr
092   */
093  Reference(final byte [] splitRow, final Range fr) {
094    this.splitkey = splitRow == null?  null: KeyValueUtil.createFirstOnRow(splitRow).getKey();
095    this.region = fr;
096  }
097
098  /**
099   * Used by serializations.
100   * @deprecated need by pb serialization
101   */
102  @Deprecated
103  // Make this private when it comes time to let go of this constructor.
104  // Needed by pb serialization.
105  public Reference() {
106    this(null, Range.bottom);
107  }
108
109  /**
110   *
111   * @return Range
112   */
113  public Range getFileRegion() {
114    return this.region;
115  }
116
117  /**
118   * @return splitKey
119   */
120  public byte [] getSplitKey() {
121    return splitkey;
122  }
123
124  /**
125   * @see java.lang.Object#toString()
126   */
127  @Override
128  public String toString() {
129    return "" + this.region;
130  }
131
132  public static boolean isTopFileRegion(final Range r) {
133    return r.equals(Range.top);
134  }
135
136  /**
137   * @deprecated Writables are going away. Use the pb serialization methods instead.
138   * Remove in a release after 0.96 goes out.  This is here only to migrate
139   * old Reference files written with Writables before 0.96.
140   */
141  @Deprecated
142  public void readFields(DataInput in) throws IOException {
143    boolean tmp = in.readBoolean();
144    // If true, set region to top.
145    this.region = tmp? Range.top: Range.bottom;
146    this.splitkey = Bytes.readByteArray(in);
147  }
148
149  public Path write(final FileSystem fs, final Path p)
150  throws IOException {
151    FSDataOutputStream out = fs.create(p, false);
152    try {
153      out.write(toByteArray());
154    } finally {
155      out.close();
156    }
157    return p;
158  }
159
160  /**
161   * Read a Reference from FileSystem.
162   * @param fs
163   * @param p
164   * @return New Reference made from passed <code>p</code>
165   * @throws IOException
166   */
167  public static Reference read(final FileSystem fs, final Path p)
168  throws IOException {
169    InputStream in = fs.open(p);
170    try {
171      // I need to be able to move back in the stream if this is not a pb serialization so I can
172      // do the Writable decoding instead.
173      in = in.markSupported()? in: new BufferedInputStream(in);
174      int pblen = ProtobufUtil.lengthOfPBMagic();
175      in.mark(pblen);
176      byte [] pbuf = new byte[pblen];
177      int read = in.read(pbuf);
178      if (read != pblen) {
179        throw new IOException("read=" + read + ", wanted=" + pblen);
180      }
181      // WATCHOUT! Return in middle of function!!!
182      if (ProtobufUtil.isPBMagicPrefix(pbuf)) return convert(FSProtos.Reference.parseFrom(in));
183      // Else presume Writables.  Need to reset the stream since it didn't start w/ pb.
184      // We won't bother rewriting thie Reference as a pb since Reference is transitory.
185      in.reset();
186      Reference r = new Reference();
187      DataInputStream dis = new DataInputStream(in);
188      // Set in = dis so it gets the close below in the finally on our way out.
189      in = dis;
190      r.readFields(dis);
191      return r;
192    } finally {
193      in.close();
194    }
195  }
196
197  public FSProtos.Reference convert() {
198    FSProtos.Reference.Builder builder = FSProtos.Reference.newBuilder();
199    builder.setRange(isTopFileRegion(getFileRegion())?
200      FSProtos.Reference.Range.TOP: FSProtos.Reference.Range.BOTTOM);
201    builder.setSplitkey(UnsafeByteOperations.unsafeWrap(getSplitKey()));
202    return builder.build();
203  }
204
205  public static Reference convert(final FSProtos.Reference r) {
206    Reference result = new Reference();
207    result.splitkey = r.getSplitkey().toByteArray();
208    result.region = r.getRange() == FSProtos.Reference.Range.TOP? Range.top: Range.bottom;
209    return result;
210  }
211
212  /**
213   * Use this when writing to a stream and you want to use the pb mergeDelimitedFrom
214   * (w/o the delimiter, pb reads to EOF which may not be what you want).
215   * @return This instance serialized as a delimited protobuf w/ a magic pb prefix.
216   * @throws IOException
217   */
218  byte [] toByteArray() throws IOException {
219    return ProtobufUtil.prependPBMagic(convert().toByteArray());
220  }
221
222  @Override
223  public int hashCode() {
224    return Arrays.hashCode(splitkey) + region.hashCode();
225  }
226
227  @Override
228  public boolean equals(Object o) {
229    if (this == o) return true;
230    if (o == null) return false;
231    if (!(o instanceof Reference)) return false;
232
233    Reference r = (Reference) o;
234    if (splitkey != null && r.splitkey == null) return false;
235    if (splitkey == null && r.splitkey != null) return false;
236    if (splitkey != null && !Arrays.equals(splitkey, r.splitkey)) return false;
237
238    return region.equals(r.region);
239  }
240}