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 * <tablename>,,<startkey>,<regionIdTimestamp>.<encodedName>. 136 * where, 137 * <encodedName> is a hex version of the MD5 hash of 138 * <tablename>,<startkey>,<regionIdTimestamp> 139 * 140 * The old region name format: 141 * <tablename>,<startkey>,<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 return RegionInfo.getRegionNameAsString(this, this.regionName); 291 } 292 293 /** @return the encoded region name */ 294 @Override 295 public String getEncodedName() { 296 return this.encodedName; 297 } 298 299 @Override 300 public byte [] getEncodedNameAsBytes() { 301 return this.encodedNameAsBytes; 302 } 303 304 /** @return the startKey */ 305 @Override 306 public byte [] getStartKey(){ 307 return startKey; 308 } 309 310 311 /** @return the endKey */ 312 @Override 313 public byte [] getEndKey(){ 314 return endKey; 315 } 316 317 /** 318 * Get current table name of the region 319 * @return TableName 320 */ 321 @Override 322 public TableName getTable() { 323 return this.tableName; 324 } 325 326 /** 327 * Returns true if the given inclusive range of rows is fully contained 328 * by this region. For example, if the region is foo,a,g and this is 329 * passed ["b","c"] or ["a","c"] it will return true, but if this is passed 330 * ["b","z"] it will return false. 331 * @throws IllegalArgumentException if the range passed is invalid (ie. end < start) 332 */ 333 @Override 334 public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) { 335 if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) { 336 throw new IllegalArgumentException( 337 "Invalid range: " + Bytes.toStringBinary(rangeStartKey) + 338 " > " + Bytes.toStringBinary(rangeEndKey)); 339 } 340 341 boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0; 342 boolean lastKeyInRange = 343 Bytes.compareTo(rangeEndKey, endKey) < 0 || 344 Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY); 345 return firstKeyInRange && lastKeyInRange; 346 } 347 348 /** 349 * Return true if the given row falls in this region. 350 */ 351 @Override 352 public boolean containsRow(byte[] row) { 353 return Bytes.compareTo(row, startKey) >= 0 && 354 (Bytes.compareTo(row, endKey) < 0 || 355 Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)); 356 } 357 358 /** @return true if this region is a meta region */ 359 @Override 360 public boolean isMetaRegion() { 361 return tableName.equals(FIRST_META_REGIONINFO.getTable()); 362 } 363 364 /** 365 * @return True if has been split and has daughters. 366 */ 367 @Override 368 public boolean isSplit() { 369 return this.split; 370 } 371 372 /** 373 * @param split set split status 374 * @return MutableRegionInfo 375 */ 376 public MutableRegionInfo setSplit(boolean split) { 377 this.split = split; 378 return this; 379 } 380 381 /** 382 * @return True if this region is offline. 383 */ 384 @Override 385 public boolean isOffline() { 386 return this.offLine; 387 } 388 389 /** 390 * The parent of a region split is offline while split daughters hold 391 * references to the parent. Offlined regions are closed. 392 * @param offLine Set online/offline status. 393 * @return MutableRegionInfo 394 */ 395 public MutableRegionInfo setOffline(boolean offLine) { 396 this.offLine = offLine; 397 return this; 398 } 399 400 /** 401 * @return True if this is a split parent region. 402 */ 403 @Override 404 public boolean isSplitParent() { 405 if (!isSplit()) return false; 406 if (!isOffline()) { 407 LOG.warn("Region is split but NOT offline: " + getRegionNameAsString()); 408 } 409 return true; 410 } 411 412 /** 413 * Returns the region replica id 414 * @return returns region replica id 415 */ 416 @Override 417 public int getReplicaId() { 418 return replicaId; 419 } 420 421 /** 422 * @see java.lang.Object#toString() 423 */ 424 @Override 425 public String toString() { 426 return "{ENCODED => " + getEncodedName() + ", " + 427 HConstants.NAME + " => '" + Bytes.toStringBinary(this.regionName) 428 + "', STARTKEY => '" + 429 Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" + 430 Bytes.toStringBinary(this.endKey) + "'" + 431 (isOffline()? ", OFFLINE => true": "") + 432 (isSplit()? ", SPLIT => true": "") + 433 ((replicaId > 0)? ", REPLICA_ID => " + replicaId : "") + "}"; 434 } 435 436 /** 437 * @param o 438 * @see java.lang.Object#equals(java.lang.Object) 439 */ 440 @Override 441 public boolean equals(Object o) { 442 if (this == o) { 443 return true; 444 } 445 if (o == null) { 446 return false; 447 } 448 if (!(o instanceof RegionInfo)) { 449 return false; 450 } 451 return this.compareTo((RegionInfo)o) == 0; 452 } 453 454 /** 455 * @see java.lang.Object#hashCode() 456 */ 457 @Override 458 public int hashCode() { 459 return this.hashCode; 460 } 461 462 @Override 463 public int compareTo(RegionInfo other) { 464 return RegionInfo.COMPARATOR.compare(this, other); 465 } 466 467 } 468}