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