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