001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.client;
019
020import java.util.Arrays;
021import org.apache.hadoop.hbase.HConstants;
022import org.apache.hadoop.hbase.TableName;
023import org.apache.hadoop.hbase.util.Bytes;
024import org.apache.yetus.audience.InterfaceAudience;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028@InterfaceAudience.Private
029public class RegionInfoBuilder {
030  private static final Logger LOG = LoggerFactory.getLogger(RegionInfoBuilder.class);
031
032  /** A non-capture group so that this can be embedded. */
033  public static final String ENCODED_REGION_NAME_REGEX = "(?:[a-f0-9]+)";
034
035  private static final int MAX_REPLICA_ID = 0xFFFF;
036
037  //TODO: Move NO_HASH to HStoreFile which is really the only place it is used.
038  public static final String NO_HASH = null;
039
040  /**
041   * RegionInfo for first meta region
042   * You cannot use this builder to make an instance of the {@link #FIRST_META_REGIONINFO}.
043   * Just refer to this instance. Also, while the instance is actually a MutableRI, its type is
044   * just RI so the mutable methods are not available (unless you go casting); it appears
045   * as immutable (I tried adding Immutable type but it just makes a mess).
046   */
047  // TODO: How come Meta regions still do not have encoded region names? Fix.
048  // hbase:meta,,1.1588230740 should be the hbase:meta first region name.
049  public static final RegionInfo FIRST_META_REGIONINFO =
050    new MutableRegionInfo(1L, TableName.META_TABLE_NAME, RegionInfo.DEFAULT_REPLICA_ID);
051
052  private final TableName tableName;
053  private byte[] startKey = HConstants.EMPTY_START_ROW;
054  private byte[] endKey = HConstants.EMPTY_END_ROW;
055  private long regionId = System.currentTimeMillis();
056  private int replicaId = RegionInfo.DEFAULT_REPLICA_ID;
057  private boolean offLine = false;
058  private boolean split = false;
059
060  public static RegionInfoBuilder newBuilder(TableName tableName) {
061    return new RegionInfoBuilder(tableName);
062  }
063
064  public static RegionInfoBuilder newBuilder(RegionInfo regionInfo) {
065    return new RegionInfoBuilder(regionInfo);
066  }
067
068  private RegionInfoBuilder(TableName tableName) {
069    this.tableName = tableName;
070  }
071
072  private RegionInfoBuilder(RegionInfo regionInfo) {
073    this.tableName = regionInfo.getTable();
074    this.startKey = regionInfo.getStartKey();
075    this.endKey = regionInfo.getEndKey();
076    this.offLine = regionInfo.isOffline();
077    this.split = regionInfo.isSplit();
078    this.regionId = regionInfo.getRegionId();
079    this.replicaId = regionInfo.getReplicaId();
080  }
081
082  public RegionInfoBuilder setStartKey(byte[] startKey) {
083    this.startKey = startKey;
084    return this;
085  }
086
087  public RegionInfoBuilder setEndKey(byte[] endKey) {
088    this.endKey = endKey;
089    return this;
090  }
091
092  public RegionInfoBuilder setRegionId(long regionId) {
093    this.regionId = regionId;
094    return this;
095  }
096
097  public RegionInfoBuilder setReplicaId(int replicaId) {
098    this.replicaId = replicaId;
099    return this;
100  }
101
102  public RegionInfoBuilder setSplit(boolean split) {
103    this.split = split;
104    return this;
105  }
106
107  public RegionInfoBuilder setOffline(boolean offLine) {
108    this.offLine = offLine;
109    return this;
110  }
111
112  public RegionInfo build() {
113    return new MutableRegionInfo(tableName, startKey, endKey, split,
114        regionId, replicaId, offLine);
115  }
116
117  /**
118   * An implementation of RegionInfo that adds mutable methods so can build a RegionInfo instance.
119   */
120  @InterfaceAudience.Private
121  static class MutableRegionInfo implements RegionInfo {
122    /**
123     * The new format for a region name contains its encodedName at the end.
124     * The encoded name also serves as the directory name for the region
125     * in the filesystem.
126     *
127     * New region name format:
128     *    <tablename>,,<startkey>,<regionIdTimestamp>.<encodedName>.
129     * where,
130     *    <encodedName> is a hex version of the MD5 hash of
131     *    <tablename>,<startkey>,<regionIdTimestamp>
132     *
133     * The old region name format:
134     *    <tablename>,<startkey>,<regionIdTimestamp>
135     * For region names in the old format, the encoded name is a 32-bit
136     * JenkinsHash integer value (in its decimal notation, string form).
137     *<p>
138     * **NOTE**
139     *
140     * The first hbase:meta region, and regions created by an older
141     * version of HBase (0.20 or prior) will continue to use the
142     * old region name format.
143     */
144
145    // This flag is in the parent of a split while the parent is still referenced by daughter
146    // regions. We USED to set this flag when we disabled a table but now table state is kept up in
147    // zookeeper as of 0.90.0 HBase. And now in DisableTableProcedure, finally we will create bunch
148    // of UnassignProcedures and at the last of the procedure we will set the region state to
149    // CLOSED, and will not change the offLine flag.
150    private boolean offLine = false;
151    private boolean split = false;
152    private final long regionId;
153    private final int replicaId;
154    private final byte[] regionName;
155    private final byte[] startKey;
156    private final byte[] endKey;
157    private final int hashCode;
158    private final String encodedName;
159    private final byte[] encodedNameAsBytes;
160    private final TableName tableName;
161
162    private static int generateHashCode(final TableName tableName, final byte[] startKey,
163        final byte[] endKey, final long regionId,
164        final int replicaId, boolean offLine, byte[] regionName) {
165      int result = Arrays.hashCode(regionName);
166      result = (int) (result ^ regionId);
167      result ^= Arrays.hashCode(checkStartKey(startKey));
168      result ^= Arrays.hashCode(checkEndKey(endKey));
169      result ^= Boolean.valueOf(offLine).hashCode();
170      result ^= Arrays.hashCode(tableName.getName());
171      result ^= replicaId;
172      return result;
173    }
174
175    private static byte[] checkStartKey(byte[] startKey) {
176      return startKey == null? HConstants.EMPTY_START_ROW: startKey;
177    }
178
179    private static byte[] checkEndKey(byte[] endKey) {
180      return endKey == null? HConstants.EMPTY_END_ROW: endKey;
181    }
182
183    private static TableName checkTableName(TableName tableName) {
184      if (tableName == null) {
185        throw new IllegalArgumentException("TableName cannot be null");
186      }
187      return tableName;
188    }
189
190    private static int checkReplicaId(int regionId) {
191      if (regionId > MAX_REPLICA_ID) {
192        throw new IllegalArgumentException("ReplicaId cannot be greater than" + MAX_REPLICA_ID);
193      }
194      return regionId;
195    }
196
197    /**
198     * Private constructor used constructing MutableRegionInfo for the
199     * first meta regions
200     */
201    private MutableRegionInfo(long regionId, TableName tableName, int replicaId) {
202      this(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, false, regionId,
203        replicaId, false);
204    }
205
206    MutableRegionInfo(final TableName tableName, final byte[] startKey, final byte[] endKey,
207      final boolean split, final long regionId, final int replicaId, boolean offLine) {
208      this.tableName = checkTableName(tableName);
209      this.startKey = checkStartKey(startKey);
210      this.endKey = checkEndKey(endKey);
211      this.split = split;
212      this.regionId = regionId;
213      this.replicaId = checkReplicaId(replicaId);
214      this.offLine = offLine;
215      this.regionName = RegionInfo.createRegionName(this.tableName, this.startKey, this.regionId,
216        this.replicaId, !this.tableName.equals(TableName.META_TABLE_NAME));
217      this.encodedName = RegionInfo.encodeRegionName(this.regionName);
218      this.hashCode = generateHashCode(this.tableName, this.startKey, this.endKey, this.regionId,
219        this.replicaId, this.offLine, this.regionName);
220      this.encodedNameAsBytes = Bytes.toBytes(this.encodedName);
221    }
222
223    /**
224     * @return Return a short, printable name for this region
225     * (usually encoded name) for us logging.
226     */
227    @Override
228    public String getShortNameToLog() {
229      return RegionInfo.prettyPrint(this.getEncodedName());
230    }
231
232    /** @return the regionId */
233    @Override
234    public long getRegionId(){
235      return regionId;
236    }
237
238
239    /**
240     * @return the regionName as an array of bytes.
241     * @see #getRegionNameAsString()
242     */
243    @Override
244    public byte[] getRegionName() {
245      return regionName;
246    }
247
248    /**
249     * @return Region name as a String for use in logging, etc.
250     */
251    @Override
252    public String getRegionNameAsString() {
253      return RegionInfo.getRegionNameAsString(this, this.regionName);
254    }
255
256    /** @return the encoded region name */
257    @Override
258    public String getEncodedName() {
259      return this.encodedName;
260    }
261
262    @Override
263    public byte[] getEncodedNameAsBytes() {
264      return this.encodedNameAsBytes;
265    }
266
267    /** @return the startKey */
268    @Override
269    public byte[] getStartKey() {
270      return startKey;
271    }
272
273    /** @return the endKey */
274    @Override
275    public byte[] getEndKey() {
276      return endKey;
277    }
278
279    /**
280     * Get current table name of the region
281     * @return TableName
282     */
283    @Override
284    public TableName getTable() {
285      return this.tableName;
286    }
287
288    /**
289     * Returns true if the given inclusive range of rows is fully contained
290     * by this region. For example, if the region is foo,a,g and this is
291     * passed ["b","c"] or ["a","c"] it will return true, but if this is passed
292     * ["b","z"] it will return false.
293     * @throws IllegalArgumentException if the range passed is invalid (ie. end &lt; start)
294     */
295    @Override
296    public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
297      if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) {
298        throw new IllegalArgumentException(
299        "Invalid range: " + Bytes.toStringBinary(rangeStartKey) +
300        " > " + Bytes.toStringBinary(rangeEndKey));
301      }
302
303      boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0;
304      boolean lastKeyInRange =
305        Bytes.compareTo(rangeEndKey, endKey) < 0 ||
306        Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY);
307      return firstKeyInRange && lastKeyInRange;
308    }
309
310    /**
311     * Return true if the given row falls in this region.
312     */
313    @Override
314    public boolean containsRow(byte[] row) {
315      return Bytes.compareTo(row, startKey) >= 0 &&
316        (Bytes.compareTo(row, endKey) < 0 ||
317         Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY));
318    }
319
320    /** @return true if this region is a meta region */
321    @Override
322    public boolean isMetaRegion() {
323       return tableName.equals(FIRST_META_REGIONINFO.getTable());
324    }
325
326    /**
327     * @return True if has been split and has daughters.
328     */
329    @Override
330    public boolean isSplit() {
331      return this.split;
332    }
333
334    /**
335     * @param split set split status
336     * @return MutableRegionInfo
337     */
338    public MutableRegionInfo setSplit(boolean split) {
339      this.split = split;
340      return this;
341    }
342
343    /**
344     * @return True if this region is offline.
345     */
346    @Override
347    public boolean isOffline() {
348      return this.offLine;
349    }
350
351    /**
352     * The parent of a region split is offline while split daughters hold
353     * references to the parent. Offlined regions are closed.
354     * @param offLine Set online/offline status.
355     * @return MutableRegionInfo
356     */
357    public MutableRegionInfo setOffline(boolean offLine) {
358      this.offLine = offLine;
359      return this;
360    }
361
362    /**
363     * @return True if this is a split parent region.
364     */
365    @Override
366    public boolean isSplitParent() {
367      if (!isSplit()) {
368        return false;
369      }
370      if (!isOffline()) {
371        LOG.warn("Region is split but NOT offline: " + getRegionNameAsString());
372      }
373      return true;
374    }
375
376    /**
377     * Returns the region replica id
378     * @return returns region replica id
379     */
380    @Override
381    public int getReplicaId() {
382      return replicaId;
383    }
384
385    /**
386     * @see java.lang.Object#toString()
387     */
388    @Override
389    public String toString() {
390      return "{ENCODED => " + getEncodedName() + ", " +
391        HConstants.NAME + " => '" + Bytes.toStringBinary(this.regionName)
392        + "', STARTKEY => '" +
393        Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" +
394        Bytes.toStringBinary(this.endKey) + "'" +
395        (isOffline()? ", OFFLINE => true": "") +
396        (isSplit()? ", SPLIT => true": "") +
397        ((replicaId > 0)? ", REPLICA_ID => " + replicaId : "") + "}";
398    }
399
400    /**
401     * @param o
402     * @see java.lang.Object#equals(java.lang.Object)
403     */
404    @Override
405    public boolean equals(Object o) {
406      if (this == o) {
407        return true;
408      }
409      if (o == null) {
410        return false;
411      }
412      if (!(o instanceof RegionInfo)) {
413        return false;
414      }
415      return compareTo((RegionInfo)o) == 0;
416    }
417
418    /**
419     * @see java.lang.Object#hashCode()
420     */
421    @Override
422    public int hashCode() {
423      return this.hashCode;
424    }
425  }
426}