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 */
018
019package org.apache.hadoop.hbase;
020
021import java.nio.ByteBuffer;
022import java.nio.charset.StandardCharsets;
023import java.util.Arrays;
024import java.util.Set;
025import java.util.concurrent.CopyOnWriteArraySet;
026
027import org.apache.hadoop.hbase.util.Bytes;
028import org.apache.yetus.audience.InterfaceAudience;
029
030/**
031 * Immutable POJO class for representing a table name.
032 * Which is of the form:
033 * <table namespace>:<table qualifier>
034 *
035 * Two special namespaces:
036 *
037 * 1. hbase - system namespace, used to contain hbase internal tables
038 * 2. default - tables with no explicit specified namespace will
039 * automatically fall into this namespace.
040 *
041 * ie
042 *
043 * a) foo:bar, means namespace=foo and qualifier=bar
044 * b) bar, means namespace=default and qualifier=bar
045 * c) default:bar, means namespace=default and qualifier=bar
046 *
047 *  <p>
048 * Internally, in this class, we cache the instances to limit the number of objects and
049 *  make the "equals" faster. We try to minimize the number of objects created of
050 *  the number of array copy to check if we already have an instance of this TableName. The code
051 *  is not optimize for a new instance creation but is optimized to check for existence.
052 * </p>
053 */
054@InterfaceAudience.Public
055public final class TableName implements Comparable<TableName> {
056
057  /** See {@link #createTableNameIfNecessary(ByteBuffer, ByteBuffer)} */
058  private static final Set<TableName> tableCache = new CopyOnWriteArraySet<>();
059
060  /** Namespace delimiter */
061  //this should always be only 1 byte long
062  public final static char NAMESPACE_DELIM = ':';
063
064  // A non-capture group so that this can be embedded.
065  // regex is a bit more complicated to support nuance of tables
066  // in default namespace
067  //Allows only letters, digits and '_'
068  public static final String VALID_NAMESPACE_REGEX =
069      "(?:[_\\p{Digit}\\p{IsAlphabetic}]+)";
070  //Allows only letters, digits, '_', '-' and '.'
071  public static final String VALID_TABLE_QUALIFIER_REGEX =
072      "(?:[_\\p{Digit}\\p{IsAlphabetic}][-_.\\p{Digit}\\p{IsAlphabetic}]*)";
073  //Concatenation of NAMESPACE_REGEX and TABLE_QUALIFIER_REGEX,
074  //with NAMESPACE_DELIM as delimiter
075  public static final String VALID_USER_TABLE_REGEX =
076      "(?:(?:(?:"+VALID_NAMESPACE_REGEX+"\\"+NAMESPACE_DELIM+")?)" +
077         "(?:"+VALID_TABLE_QUALIFIER_REGEX+"))";
078
079  /** The hbase:meta table's name. */
080  public static final TableName META_TABLE_NAME =
081      valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "meta");
082
083  /** The Namespace table's name. */
084  public static final TableName NAMESPACE_TABLE_NAME =
085      valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "namespace");
086
087  public static final String OLD_META_STR = ".META.";
088  public static final String OLD_ROOT_STR = "-ROOT-";
089
090  /** One globally disallowed name */
091  public static final String DISALLOWED_TABLE_NAME = "zookeeper";
092
093  /**
094   * @return True if <code>tn</code> is the hbase:meta table name.
095   */
096  public static boolean isMetaTableName(final TableName tn) {
097    return tn.equals(TableName.META_TABLE_NAME);
098  }
099
100  /**
101   * TableName for old -ROOT- table. It is used to read/process old WALs which have
102   * ROOT edits.
103   */
104  public static final TableName OLD_ROOT_TABLE_NAME = getADummyTableName(OLD_ROOT_STR);
105  /**
106   * TableName for old .META. table. Used in testing.
107   */
108  public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR);
109
110  private final byte[] name;
111  private final String nameAsString;
112  private final byte[] namespace;
113  private final String namespaceAsString;
114  private final byte[] qualifier;
115  private final String qualifierAsString;
116  private final boolean systemTable;
117  private final int hashCode;
118
119  /**
120   * Check passed byte array, "tableName", is legal user-space table name.
121   * @return Returns passed <code>tableName</code> param
122   * @throws IllegalArgumentException if passed a tableName is null or
123   * is made of other than 'word' characters or underscores: i.e.
124   * <code>[\p{IsAlphabetic}\p{Digit}.-:]</code>. The ':' is used to delimit the namespace
125   * from the table name and can be used for nothing else.
126   *
127   * Namespace names can only contain 'word' characters
128   * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_'
129   *
130   * Qualifier names can only contain 'word' characters
131   * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_', '.' or '-'.
132   * The name may not start with '.' or '-'.
133   *
134   * Valid fully qualified table names:
135   * foo:bar, namespace=&gt;foo, table=&gt;bar
136   * org:foo.bar, namespace=org, table=&gt;foo.bar
137   */
138  public static byte [] isLegalFullyQualifiedTableName(final byte[] tableName) {
139    if (tableName == null || tableName.length <= 0) {
140      throw new IllegalArgumentException("Name is null or empty");
141    }
142
143    int namespaceDelimIndex =
144      org.apache.hbase.thirdparty.com.google.common.primitives.Bytes.lastIndexOf(tableName,
145        (byte) NAMESPACE_DELIM);
146    if (namespaceDelimIndex < 0){
147      isLegalTableQualifierName(tableName);
148    } else {
149      isLegalNamespaceName(tableName, 0, namespaceDelimIndex);
150      isLegalTableQualifierName(tableName, namespaceDelimIndex + 1, tableName.length);
151    }
152    return tableName;
153  }
154
155  public static byte [] isLegalTableQualifierName(final byte[] qualifierName) {
156    isLegalTableQualifierName(qualifierName, 0, qualifierName.length, false);
157    return qualifierName;
158  }
159
160  public static byte [] isLegalTableQualifierName(final byte[] qualifierName, boolean isSnapshot) {
161    isLegalTableQualifierName(qualifierName, 0, qualifierName.length, isSnapshot);
162    return qualifierName;
163  }
164
165
166  /**
167   * Qualifier names can only contain 'word' characters
168   * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_', '.' or '-'.
169   * The name may not start with '.' or '-'.
170   *
171   * @param qualifierName byte array containing the qualifier name
172   * @param start start index
173   * @param end end index (exclusive)
174   */
175  public static void isLegalTableQualifierName(final byte[] qualifierName,
176                                                int start,
177                                                int end) {
178      isLegalTableQualifierName(qualifierName, start, end, false);
179  }
180
181  public static void isLegalTableQualifierName(final byte[] qualifierName,
182                                                int start,
183                                                int end,
184                                                boolean isSnapshot) {
185    if(end - start < 1) {
186      throw new IllegalArgumentException(isSnapshot ? "Snapshot" : "Table" + " qualifier must not be empty");
187    }
188    if (qualifierName[start] == '.' || qualifierName[start] == '-') {
189      throw new IllegalArgumentException("Illegal first character <" + qualifierName[start] +
190                                         "> at 0. " + (isSnapshot ? "Snapshot" : "User-space table") +
191                                         " qualifiers can only start with 'alphanumeric " +
192                                         "characters' from any language: " +
193                                         Bytes.toString(qualifierName, start, end));
194    }
195    // Treat the bytes as UTF-8
196    String qualifierString = new String(
197        qualifierName, start, (end - start), StandardCharsets.UTF_8);
198    if (qualifierString.equals(DISALLOWED_TABLE_NAME)) {
199      // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
200      // A znode named "zookeeper" is disallowed by zookeeper.
201      throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'");
202    }
203    for (int i = 0; i < qualifierString.length(); i++) {
204      // Treat the string as a char-array as some characters may be multi-byte
205      char c = qualifierString.charAt(i);
206      // Check for letter, digit, underscore, hyphen, or period, and allowed by ZK.
207      // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
208      //   See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
209      if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_' || c == '-' || c == '.') {
210        continue;
211      }
212      throw new IllegalArgumentException("Illegal character code:" + (int) c + ", <" + c + "> at " +
213          i + ". " + (isSnapshot ? "Snapshot" : "User-space table") +
214          " qualifiers may only contain 'alphanumeric characters' and digits: " +
215          qualifierString);
216    }
217  }
218
219  public static void isLegalNamespaceName(byte[] namespaceName) {
220    isLegalNamespaceName(namespaceName, 0, namespaceName.length);
221  }
222
223  /**
224   * Valid namespace characters are alphabetic characters, numbers, and underscores.
225   */
226  public static void isLegalNamespaceName(final byte[] namespaceName,
227                                           final int start,
228                                           final int end) {
229    if(end - start < 1) {
230      throw new IllegalArgumentException("Namespace name must not be empty");
231    }
232    String nsString = new String(namespaceName, start, (end - start), StandardCharsets.UTF_8);
233    if (nsString.equals(DISALLOWED_TABLE_NAME)) {
234      // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
235      // A znode named "zookeeper" is disallowed by zookeeper.
236      throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'");
237    }
238    for (int i = 0; i < nsString.length(); i++) {
239      // Treat the string as a char-array as some characters may be multi-byte
240      char c = nsString.charAt(i);
241      // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
242      //   See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
243      if (Character.isAlphabetic(c) || Character.isDigit(c)|| c == '_') {
244        continue;
245      }
246      throw new IllegalArgumentException("Illegal character <" + c +
247        "> at " + i + ". Namespaces may only contain " +
248        "'alphanumeric characters' from any language and digits: " + nsString);
249    }
250  }
251
252  public byte[] getName() {
253    return name;
254  }
255
256  public String getNameAsString() {
257    return nameAsString;
258  }
259
260  public byte[] getNamespace() {
261    return namespace;
262  }
263
264  public String getNamespaceAsString() {
265    return namespaceAsString;
266  }
267
268  /**
269   * Ideally, getNameAsString should contain namespace within it,
270   * but if the namespace is default, it just returns the name. This method
271   * takes care of this corner case.
272   */
273  public String getNameWithNamespaceInclAsString() {
274    if(getNamespaceAsString().equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR)) {
275      return NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR +
276          TableName.NAMESPACE_DELIM + getNameAsString();
277    }
278    return getNameAsString();
279  }
280
281  public byte[] getQualifier() {
282    return qualifier;
283  }
284
285  public String getQualifierAsString() {
286    return qualifierAsString;
287  }
288
289  public byte[] toBytes() {
290    return name;
291  }
292
293  public boolean isSystemTable() {
294    return systemTable;
295  }
296
297  @Override
298  public String toString() {
299    return nameAsString;
300  }
301
302  /**
303   *
304   * @throws IllegalArgumentException See {@link #valueOf(byte[])}
305   */
306  private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException {
307    this.qualifier = new byte[qualifier.remaining()];
308    qualifier.duplicate().get(this.qualifier);
309    this.qualifierAsString = Bytes.toString(this.qualifier);
310
311    if (qualifierAsString.equals(OLD_ROOT_STR)) {
312      throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated.");
313    }
314    if (qualifierAsString.equals(OLD_META_STR)) {
315      throw new IllegalArgumentException(OLD_META_STR + " no longer exists. The table has been " +
316          "renamed to " + META_TABLE_NAME);
317    }
318
319    if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) {
320      // Using the same objects: this will make the comparison faster later
321      this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
322      this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
323      this.systemTable = false;
324
325      // The name does not include the namespace when it's the default one.
326      this.nameAsString = qualifierAsString;
327      this.name = this.qualifier;
328    } else {
329      if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) {
330        this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
331        this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
332        this.systemTable = true;
333      } else {
334        this.namespace = new byte[namespace.remaining()];
335        namespace.duplicate().get(this.namespace);
336        this.namespaceAsString = Bytes.toString(this.namespace);
337        this.systemTable = false;
338      }
339      this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
340      this.name = Bytes.toBytes(nameAsString);
341    }
342
343    this.hashCode = nameAsString.hashCode();
344
345    isLegalNamespaceName(this.namespace);
346    isLegalTableQualifierName(this.qualifier);
347  }
348
349  /**
350   * This is only for the old and meta tables.
351   */
352  private TableName(String qualifier) {
353    this.qualifier = Bytes.toBytes(qualifier);
354    this.qualifierAsString = qualifier;
355
356    this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
357    this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
358    this.systemTable = true;
359
360    // WARNING: nameAsString is different than name for old meta & root!
361    // This is by design.
362    this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
363    this.name = this.qualifier;
364
365    this.hashCode = nameAsString.hashCode();
366  }
367
368
369  /**
370   * Check that the object does not exist already. There are two reasons for creating the objects
371   * only once:
372   * 1) With 100K regions, the table names take ~20MB.
373   * 2) Equals becomes much faster as it's resolved with a reference and an int comparison.
374   */
375  private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) {
376    for (TableName tn : tableCache) {
377      if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
378        return tn;
379      }
380    }
381
382    TableName newTable = new TableName(bns, qns);
383    if (tableCache.add(newTable)) {  // Adds the specified element if it is not already present
384      return newTable;
385    }
386
387    // Someone else added it. Let's find it.
388    for (TableName tn : tableCache) {
389      if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
390        return tn;
391      }
392    }
393    // this should never happen.
394    throw new IllegalStateException(newTable + " was supposed to be in the cache");
395  }
396
397
398  /**
399   * It is used to create table names for old META, and ROOT table.
400   * These tables are not really legal tables. They are not added into the cache.
401   * @return a dummy TableName instance (with no validation) for the passed qualifier
402   */
403  private static TableName getADummyTableName(String qualifier) {
404    return new TableName(qualifier);
405  }
406
407
408  public static TableName valueOf(String namespaceAsString, String qualifierAsString) {
409    if (namespaceAsString == null || namespaceAsString.length() < 1) {
410      namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
411    }
412
413    for (TableName tn : tableCache) {
414      if (qualifierAsString.equals(tn.getQualifierAsString()) &&
415          namespaceAsString.equals(tn.getNamespaceAsString())) {
416        return tn;
417      }
418    }
419
420    return createTableNameIfNecessary(
421        ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)),
422        ByteBuffer.wrap(Bytes.toBytes(qualifierAsString)));
423  }
424
425
426  /**
427   * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
428   *  depends on this. The test is buried in the table creation to save on array comparison
429   *  when we're creating a standard table object that will be in the cache.
430   */
431  public static TableName valueOf(byte[] fullName) throws IllegalArgumentException{
432    for (TableName tn : tableCache) {
433      if (Arrays.equals(tn.getName(), fullName)) {
434        return tn;
435      }
436    }
437
438    int namespaceDelimIndex =
439      org.apache.hbase.thirdparty.com.google.common.primitives.Bytes.lastIndexOf(fullName,
440        (byte) NAMESPACE_DELIM);
441
442    if (namespaceDelimIndex < 0) {
443      return createTableNameIfNecessary(
444          ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
445          ByteBuffer.wrap(fullName));
446    } else {
447      return createTableNameIfNecessary(
448          ByteBuffer.wrap(fullName, 0, namespaceDelimIndex),
449          ByteBuffer.wrap(fullName, namespaceDelimIndex + 1,
450              fullName.length - (namespaceDelimIndex + 1)));
451    }
452  }
453
454
455  /**
456   * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
457   *  depends on this.
458   */
459  public static TableName valueOf(String name) {
460    for (TableName tn : tableCache) {
461      if (name.equals(tn.getNameAsString())) {
462        return tn;
463      }
464    }
465
466    final int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM);
467
468    if (namespaceDelimIndex < 0) {
469      return createTableNameIfNecessary(
470          ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
471          ByteBuffer.wrap(Bytes.toBytes(name)));
472    } else {
473      // indexOf is by character, not byte (consider multi-byte characters)
474      String ns = name.substring(0, namespaceDelimIndex);
475      String qualifier = name.substring(namespaceDelimIndex + 1);
476      return createTableNameIfNecessary(
477          ByteBuffer.wrap(Bytes.toBytes(ns)),
478          ByteBuffer.wrap(Bytes.toBytes(qualifier)));
479    }
480  }
481
482
483  public static TableName valueOf(byte[] namespace, byte[] qualifier) {
484    if (namespace == null || namespace.length < 1) {
485      namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
486    }
487
488    for (TableName tn : tableCache) {
489      if (Arrays.equals(tn.getQualifier(), qualifier) &&
490          Arrays.equals(tn.getNamespace(), namespace)) {
491        return tn;
492      }
493    }
494
495    return createTableNameIfNecessary(
496        ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier));
497  }
498
499  public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) {
500    if (namespace == null || namespace.remaining() < 1) {
501      return createTableNameIfNecessary(
502          ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), qualifier);
503    }
504
505    return createTableNameIfNecessary(namespace, qualifier);
506  }
507
508  @Override
509  public boolean equals(Object o) {
510    if (this == o) return true;
511    if (o == null || getClass() != o.getClass()) return false;
512
513    TableName tableName = (TableName) o;
514
515    return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString);
516  }
517
518  @Override
519  public int hashCode() {
520    return hashCode;
521  }
522
523  /**
524   * For performance reasons, the ordering is not lexicographic.
525   */
526  @Override
527  public int compareTo(TableName tableName) {
528    if (this == tableName) return 0;
529    if (this.hashCode < tableName.hashCode()) {
530      return -1;
531    }
532    if (this.hashCode > tableName.hashCode()) {
533      return 1;
534    }
535    return this.nameAsString.compareTo(tableName.getNameAsString());
536  }
537
538}