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 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 < 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}