1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase;
20  
21  import java.io.DataInput;
22  import java.io.DataOutput;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.TreeMap;
34  import java.util.TreeSet;
35  import java.util.regex.Matcher;
36  
37  import org.apache.hadoop.classification.InterfaceAudience;
38  import org.apache.hadoop.classification.InterfaceStability;
39  import org.apache.hadoop.fs.Path;
40  import org.apache.hadoop.hbase.exceptions.DeserializationException;
41  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
42  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
43  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.BytesBytesPair;
44  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.ColumnFamilySchema;
45  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.NameStringPair;
46  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.TableSchema;
47  import org.apache.hadoop.hbase.regionserver.BloomType;
48  import org.apache.hadoop.hbase.security.User;
49  import org.apache.hadoop.hbase.util.Bytes;
50  import org.apache.hadoop.hbase.util.Writables;
51  import org.apache.hadoop.io.WritableComparable;
52  
53  import com.google.protobuf.ByteString;
54  import com.google.protobuf.InvalidProtocolBufferException;
55  
56  /**
57   * HTableDescriptor contains the details about an HBase table  such as the descriptors of
58   * all the column families, is the table a catalog table, <code> -ROOT- </code> or
59   * <code> .META. </code>, if the table is read only, the maximum size of the memstore,
60   * when the region split should occur, coprocessors associated with it etc...
61   */
62  @InterfaceAudience.Public
63  @InterfaceStability.Evolving
64  public class HTableDescriptor implements WritableComparable<HTableDescriptor> {
65  
66    /**
67     *  Changes prior to version 3 were not recorded here.
68     *  Version 3 adds metadata as a map where keys and values are byte[].
69     *  Version 4 adds indexes
70     *  Version 5 removed transactional pollution -- e.g. indexes
71     *  Version 6 changed metadata to BytesBytesPair in PB
72     *  Version 7 adds table-level configuration
73     */
74    private static final byte TABLE_DESCRIPTOR_VERSION = 7;
75  
76    private byte [] name = HConstants.EMPTY_BYTE_ARRAY;
77  
78    private String nameAsString = "";
79  
80    /**
81     * A map which holds the metadata information of the table. This metadata
82     * includes values like IS_ROOT, IS_META, DEFERRED_LOG_FLUSH, SPLIT_POLICY,
83     * MAX_FILE_SIZE, READONLY, MEMSTORE_FLUSHSIZE etc...
84     */
85    private final Map<ImmutableBytesWritable, ImmutableBytesWritable> values =
86      new HashMap<ImmutableBytesWritable, ImmutableBytesWritable>();
87  
88    /**
89     * A map which holds the configuration specific to the table.
90     * The keys of the map have the same names as config keys and override the defaults with
91     * table-specific settings. Example usage may be for compactions, etc.
92     */
93    private final Map<String, String> configuration = new HashMap<String, String>();
94  
95    public static final String SPLIT_POLICY = "SPLIT_POLICY";
96  
97    /**
98     * <em>INTERNAL</em> Used by HBase Shell interface to access this metadata
99     * attribute which denotes the maximum size of the store file after which
100    * a region split occurs
101    *
102    * @see #getMaxFileSize()
103    */
104   public static final String MAX_FILESIZE = "MAX_FILESIZE";
105   private static final ImmutableBytesWritable MAX_FILESIZE_KEY =
106     new ImmutableBytesWritable(Bytes.toBytes(MAX_FILESIZE));
107 
108   public static final String OWNER = "OWNER";
109   public static final ImmutableBytesWritable OWNER_KEY =
110     new ImmutableBytesWritable(Bytes.toBytes(OWNER));
111 
112   /**
113    * <em>INTERNAL</em> Used by rest interface to access this metadata
114    * attribute which denotes if the table is Read Only
115    *
116    * @see #isReadOnly()
117    */
118   public static final String READONLY = "READONLY";
119   private static final ImmutableBytesWritable READONLY_KEY =
120     new ImmutableBytesWritable(Bytes.toBytes(READONLY));
121 
122   /**
123    * <em>INTERNAL</em> Used by HBase Shell interface to access this metadata
124    * attribute which represents the maximum size of the memstore after which
125    * its contents are flushed onto the disk
126    *
127    * @see #getMemStoreFlushSize()
128    */
129   public static final String MEMSTORE_FLUSHSIZE = "MEMSTORE_FLUSHSIZE";
130   private static final ImmutableBytesWritable MEMSTORE_FLUSHSIZE_KEY =
131     new ImmutableBytesWritable(Bytes.toBytes(MEMSTORE_FLUSHSIZE));
132 
133   /**
134    * <em>INTERNAL</em> Used by rest interface to access this metadata
135    * attribute which denotes if the table is a -ROOT- region or not
136    *
137    * @see #isRootRegion()
138    */
139   public static final String IS_ROOT = "IS_ROOT";
140   private static final ImmutableBytesWritable IS_ROOT_KEY =
141     new ImmutableBytesWritable(Bytes.toBytes(IS_ROOT));
142 
143   /**
144    * <em>INTERNAL</em> Used by rest interface to access this metadata
145    * attribute which denotes if it is a catalog table, either
146    * <code> .META. </code> or <code> -ROOT- </code>
147    *
148    * @see #isMetaRegion()
149    */
150   public static final String IS_META = "IS_META";
151   private static final ImmutableBytesWritable IS_META_KEY =
152     new ImmutableBytesWritable(Bytes.toBytes(IS_META));
153 
154   /**
155    * <em>INTERNAL</em> Used by HBase Shell interface to access this metadata
156    * attribute which denotes if the deferred log flush option is enabled
157    */
158   public static final String DEFERRED_LOG_FLUSH = "DEFERRED_LOG_FLUSH";
159   private static final ImmutableBytesWritable DEFERRED_LOG_FLUSH_KEY =
160     new ImmutableBytesWritable(Bytes.toBytes(DEFERRED_LOG_FLUSH));
161 
162   /*
163    *  The below are ugly but better than creating them each time till we
164    *  replace booleans being saved as Strings with plain booleans.  Need a
165    *  migration script to do this.  TODO.
166    */
167   private static final ImmutableBytesWritable FALSE =
168     new ImmutableBytesWritable(Bytes.toBytes(Boolean.FALSE.toString()));
169 
170   private static final ImmutableBytesWritable TRUE =
171     new ImmutableBytesWritable(Bytes.toBytes(Boolean.TRUE.toString()));
172 
173   private static final boolean DEFAULT_DEFERRED_LOG_FLUSH = false;
174 
175   /**
176    * Constant that denotes whether the table is READONLY by default and is false
177    */
178   public static final boolean DEFAULT_READONLY = false;
179 
180   /**
181    * Constant that denotes the maximum default size of the memstore after which
182    * the contents are flushed to the store files
183    */
184   public static final long DEFAULT_MEMSTORE_FLUSH_SIZE = 1024*1024*128L;
185 
186   private final static Map<String, String> DEFAULT_VALUES
187     = new HashMap<String, String>();
188   private final static Set<ImmutableBytesWritable> RESERVED_KEYWORDS
189     = new HashSet<ImmutableBytesWritable>();
190   static {
191     DEFAULT_VALUES.put(MAX_FILESIZE,
192         String.valueOf(HConstants.DEFAULT_MAX_FILE_SIZE));
193     DEFAULT_VALUES.put(READONLY, String.valueOf(DEFAULT_READONLY));
194     DEFAULT_VALUES.put(MEMSTORE_FLUSHSIZE,
195         String.valueOf(DEFAULT_MEMSTORE_FLUSH_SIZE));
196     DEFAULT_VALUES.put(DEFERRED_LOG_FLUSH,
197         String.valueOf(DEFAULT_DEFERRED_LOG_FLUSH));
198     for (String s : DEFAULT_VALUES.keySet()) {
199       RESERVED_KEYWORDS.add(new ImmutableBytesWritable(Bytes.toBytes(s)));
200     }
201     RESERVED_KEYWORDS.add(IS_ROOT_KEY);
202     RESERVED_KEYWORDS.add(IS_META_KEY);
203   }
204 
205   /**
206    * Cache of whether this is a meta table or not.
207    */
208   private volatile Boolean meta = null;
209   /**
210    * Cache of whether this is root table or not.
211    */
212   private volatile Boolean root = null;
213   /**
214    * Cache of whether deferred logging set.
215    */
216   private Boolean deferredLog = null;
217 
218   /**
219    * Maps column family name to the respective HColumnDescriptors
220    */
221   private final Map<byte [], HColumnDescriptor> families =
222     new TreeMap<byte [], HColumnDescriptor>(Bytes.BYTES_RAWCOMPARATOR);
223 
224   /**
225    * <em> INTERNAL </em> Private constructor used internally creating table descriptors for
226    * catalog tables, <code>.META.</code> and <code>-ROOT-</code>.
227    */
228   protected HTableDescriptor(final byte [] name, HColumnDescriptor[] families) {
229     this.name = name.clone();
230     this.nameAsString = Bytes.toString(this.name);
231     setMetaFlags(name);
232     for(HColumnDescriptor descriptor : families) {
233       this.families.put(descriptor.getName(), descriptor);
234     }
235   }
236 
237   /**
238    * <em> INTERNAL </em>Private constructor used internally creating table descriptors for
239    * catalog tables, <code>.META.</code> and <code>-ROOT-</code>.
240    */
241   protected HTableDescriptor(final byte [] name, HColumnDescriptor[] families,
242       Map<ImmutableBytesWritable,ImmutableBytesWritable> values) {
243     this.name = name.clone();
244     this.nameAsString = Bytes.toString(this.name);
245     setMetaFlags(name);
246     for(HColumnDescriptor descriptor : families) {
247       this.families.put(descriptor.getName(), descriptor);
248     }
249     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> entry:
250         values.entrySet()) {
251       setValue(entry.getKey(), entry.getValue());
252     }
253   }
254 
255   /**
256    * Default constructor which constructs an empty object.
257    * For deserializing an HTableDescriptor instance only.
258    * @see #HTableDescriptor(byte[])
259    * @deprecated Used by Writables and Writables are going away.
260    */
261   @Deprecated
262   public HTableDescriptor() {
263     super();
264   }
265 
266   /**
267    * Construct a table descriptor specifying table name.
268    * @param name Table name.
269    * @throws IllegalArgumentException if passed a table name
270    * that is made of other than 'word' characters, underscore or period: i.e.
271    * <code>[a-zA-Z_0-9.].
272    * @see <a href="HADOOP-1581">HADOOP-1581 HBASE: Un-openable tablename bug</a>
273    */
274   public HTableDescriptor(final String name) {
275     this(Bytes.toBytes(name));
276   }
277 
278   /**
279    * Construct a table descriptor specifying a byte array table name
280    * @param name - Table name as a byte array.
281    * @throws IllegalArgumentException if passed a table name
282    * that is made of other than 'word' characters, underscore or period: i.e.
283    * <code>[a-zA-Z_0-9-.].
284    * @see <a href="HADOOP-1581">HADOOP-1581 HBASE: Un-openable tablename bug</a>
285    */
286   public HTableDescriptor(final byte [] name) {
287     super();
288     setMetaFlags(this.name);
289     this.name = this.isMetaRegion()? name: isLegalTableName(name);
290     this.nameAsString = Bytes.toString(this.name);
291   }
292 
293   /**
294    * Construct a table descriptor by cloning the descriptor passed as a parameter.
295    * <p>
296    * Makes a deep copy of the supplied descriptor.
297    * Can make a modifiable descriptor from an UnmodifyableHTableDescriptor.
298    * @param desc The descriptor.
299    */
300   public HTableDescriptor(final HTableDescriptor desc) {
301     super();
302     this.name = desc.name.clone();
303     this.nameAsString = Bytes.toString(this.name);
304     setMetaFlags(this.name);
305     for (HColumnDescriptor c: desc.families.values()) {
306       this.families.put(c.getName(), new HColumnDescriptor(c));
307     }
308     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
309         desc.values.entrySet()) {
310       setValue(e.getKey(), e.getValue());
311     }
312     for (Map.Entry<String, String> e : desc.configuration.entrySet()) {
313       this.configuration.put(e.getKey(), e.getValue());
314     }
315   }
316 
317   /*
318    * Set meta flags on this table.
319    * IS_ROOT_KEY is set if its a -ROOT- table
320    * IS_META_KEY is set either if its a -ROOT- or a .META. table
321    * Called by constructors.
322    * @param name
323    */
324   private void setMetaFlags(final byte [] name) {
325     setRootRegion(Bytes.equals(name, HConstants.ROOT_TABLE_NAME));
326     setMetaRegion(isRootRegion() ||
327       Bytes.equals(name, HConstants.META_TABLE_NAME));
328   }
329 
330   /**
331    * Check if the descriptor represents a <code> -ROOT- </code> region.
332    *
333    * @return true if this is a <code> -ROOT- </code> region
334    */
335   public boolean isRootRegion() {
336     if (this.root == null) {
337       this.root = isSomething(IS_ROOT_KEY, false)? Boolean.TRUE: Boolean.FALSE;
338     }
339     return this.root.booleanValue();
340   }
341 
342   /**
343    * <em> INTERNAL </em> Used to denote if the current table represents
344    * <code> -ROOT- </code> region. This is used internally by the
345    * HTableDescriptor constructors
346    *
347    * @param isRoot true if this is the <code> -ROOT- </code> region
348    */
349   protected void setRootRegion(boolean isRoot) {
350     // TODO: Make the value a boolean rather than String of boolean.
351     setValue(IS_ROOT_KEY, isRoot? TRUE: FALSE);
352   }
353 
354   /**
355    * Checks if this table is either <code> -ROOT- </code> or <code> .META. </code>
356    * region.
357    *
358    * @return true if this is either a <code> -ROOT- </code> or <code> .META. </code>
359    * region
360    */
361   public boolean isMetaRegion() {
362     if (this.meta == null) {
363       this.meta = calculateIsMetaRegion();
364     }
365     return this.meta.booleanValue();
366   }
367 
368   private synchronized Boolean calculateIsMetaRegion() {
369     byte [] value = getValue(IS_META_KEY);
370     return (value != null)? Boolean.valueOf(Bytes.toString(value)): Boolean.FALSE;
371   }
372 
373   private boolean isSomething(final ImmutableBytesWritable key,
374       final boolean valueIfNull) {
375     byte [] value = getValue(key);
376     if (value != null) {
377       // TODO: Make value be a boolean rather than String of boolean.
378       return Boolean.valueOf(Bytes.toString(value));
379     }
380     return valueIfNull;
381   }
382 
383   /**
384    * <em> INTERNAL </em> Used to denote if the current table represents
385    * <code> -ROOT- </code> or <code> .META. </code> region. This is used
386    * internally by the HTableDescriptor constructors
387    *
388    * @param isMeta true if its either <code> -ROOT- </code> or
389    * <code> .META. </code> region
390    */
391   protected void setMetaRegion(boolean isMeta) {
392     setValue(IS_META_KEY, isMeta? TRUE: FALSE);
393   }
394 
395   /**
396    * Checks if the table is a <code>.META.</code> table
397    *
398    * @return true if table is <code> .META. </code> region.
399    */
400   public boolean isMetaTable() {
401     return isMetaRegion() && !isRootRegion();
402   }
403 
404   /**
405    * Checks of the tableName being passed represents either
406    * <code > -ROOT- </code> or <code> .META. </code>
407    *
408    * @return true if a tablesName is either <code> -ROOT- </code>
409    * or <code> .META. </code>
410    */
411   public static boolean isMetaTable(final byte [] tableName) {
412     return Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME) ||
413       Bytes.equals(tableName, HConstants.META_TABLE_NAME);
414   }
415 
416   // A non-capture group so that this can be embedded.
417   public static final String VALID_USER_TABLE_REGEX = "(?:[a-zA-Z_0-9][a-zA-Z_0-9.-]*)";
418 
419   /**
420    * Check passed byte buffer, "tableName", is legal user-space table name.
421    * @return Returns passed <code>tableName</code> param
422    * @throws NullPointerException If passed <code>tableName</code> is null
423    * @throws IllegalArgumentException if passed a tableName
424    * that is made of other than 'word' characters or underscores: i.e.
425    * <code>[a-zA-Z_0-9].
426    */
427   public static byte [] isLegalTableName(final byte [] tableName) {
428     if (tableName == null || tableName.length <= 0) {
429       throw new IllegalArgumentException("Name is null or empty");
430     }
431     if (tableName[0] == '.' || tableName[0] == '-') {
432       throw new IllegalArgumentException("Illegal first character <" + tableName[0] +
433           "> at 0. User-space table names can only start with 'word " +
434           "characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(tableName));
435     }
436     if (HConstants.CLUSTER_ID_FILE_NAME.equalsIgnoreCase(Bytes
437         .toString(tableName))
438         || HConstants.SPLIT_LOGDIR_NAME.equalsIgnoreCase(Bytes
439             .toString(tableName))
440         || HConstants.VERSION_FILE_NAME.equalsIgnoreCase(Bytes
441             .toString(tableName))) {
442       throw new IllegalArgumentException(Bytes.toString(tableName)
443           + " conflicted with system reserved words");
444     }
445     for (int i = 0; i < tableName.length; i++) {
446       if (Character.isLetterOrDigit(tableName[i]) || tableName[i] == '_' ||
447     		  tableName[i] == '-' || tableName[i] == '.') {
448         continue;
449       }
450       throw new IllegalArgumentException("Illegal character <" + tableName[i] +
451         "> at " + i + ". User-space table names can only contain " +
452         "'word characters': i.e. [a-zA-Z_0-9-.]: " + Bytes.toString(tableName));
453     }
454     return tableName;
455   }
456 
457   /**
458    * Getter for accessing the metadata associated with the key
459    *
460    * @param key The key.
461    * @return The value.
462    * @see #values
463    */
464   public byte[] getValue(byte[] key) {
465     return getValue(new ImmutableBytesWritable(key));
466   }
467 
468   private byte[] getValue(final ImmutableBytesWritable key) {
469     ImmutableBytesWritable ibw = values.get(key);
470     if (ibw == null)
471       return null;
472     return ibw.get();
473   }
474 
475   /**
476    * Getter for accessing the metadata associated with the key
477    *
478    * @param key The key.
479    * @return The value.
480    * @see #values
481    */
482   public String getValue(String key) {
483     byte[] value = getValue(Bytes.toBytes(key));
484     if (value == null)
485       return null;
486     return Bytes.toString(value);
487   }
488 
489   /**
490    * Getter for fetching an unmodifiable {@link #values} map.
491    *
492    * @return unmodifiable map {@link #values}.
493    * @see #values
494    */
495   public Map<ImmutableBytesWritable,ImmutableBytesWritable> getValues() {
496     // shallow pointer copy
497     return Collections.unmodifiableMap(values);
498   }
499 
500   /**
501    * Setter for storing metadata as a (key, value) pair in {@link #values} map
502    *
503    * @param key The key.
504    * @param value The value.
505    * @see #values
506    */
507   public void setValue(byte[] key, byte[] value) {
508     setValue(new ImmutableBytesWritable(key), new ImmutableBytesWritable(value));
509   }
510 
511   /*
512    * @param key The key.
513    * @param value The value.
514    */
515   private void setValue(final ImmutableBytesWritable key,
516       final String value) {
517     setValue(key, new ImmutableBytesWritable(Bytes.toBytes(value)));
518   }
519 
520   /*
521    * @param key The key.
522    * @param value The value.
523    */
524   private void setValue(final ImmutableBytesWritable key,
525       final ImmutableBytesWritable value) {
526     values.put(key, value);
527   }
528 
529   /**
530    * Setter for storing metadata as a (key, value) pair in {@link #values} map
531    *
532    * @param key The key.
533    * @param value The value.
534    * @see #values
535    */
536   public void setValue(String key, String value) {
537     if (value == null) {
538       remove(key);
539     } else {
540       setValue(Bytes.toBytes(key), Bytes.toBytes(value));
541     }
542   }
543 
544   /**
545    * Remove metadata represented by the key from the {@link #values} map
546    *
547    * @param key Key whose key and value we're to remove from HTableDescriptor
548    * parameters.
549    */
550   public void remove(final String key) {
551     remove(new ImmutableBytesWritable(Bytes.toBytes(key)));
552   }
553 
554   /**
555    * Remove metadata represented by the key from the {@link #values} map
556    *
557    * @param key Key whose key and value we're to remove from HTableDescriptor
558    * parameters.
559    */
560   public void remove(ImmutableBytesWritable key) {
561     values.remove(key);
562   }
563 
564   /**
565    * Check if the readOnly flag of the table is set. If the readOnly flag is
566    * set then the contents of the table can only be read from but not modified.
567    *
568    * @return true if all columns in the table should be read only
569    */
570   public boolean isReadOnly() {
571     return isSomething(READONLY_KEY, DEFAULT_READONLY);
572   }
573 
574   /**
575    * Setting the table as read only sets all the columns in the table as read
576    * only. By default all tables are modifiable, but if the readOnly flag is
577    * set to true then the contents of the table can only be read but not modified.
578    *
579    * @param readOnly True if all of the columns in the table should be read
580    * only.
581    */
582   public void setReadOnly(final boolean readOnly) {
583     setValue(READONLY_KEY, readOnly? TRUE: FALSE);
584   }
585 
586   /**
587    * Check if deferred log edits are enabled on the table.
588    *
589    * @return true if that deferred log flush is enabled on the table
590    *
591    * @see #setDeferredLogFlush(boolean)
592    */
593   public synchronized boolean isDeferredLogFlush() {
594     if(this.deferredLog == null) {
595       this.deferredLog =
596           isSomething(DEFERRED_LOG_FLUSH_KEY, DEFAULT_DEFERRED_LOG_FLUSH);
597     }
598     return this.deferredLog;
599   }
600 
601   /**
602    * This is used to defer the log edits syncing to the file system. Everytime
603    * an edit is sent to the server it is first sync'd to the file system by the
604    * log writer. This sync is an expensive operation and thus can be deferred so
605    * that the edits are kept in memory for a specified period of time as represented
606    * by <code> hbase.regionserver.optionallogflushinterval </code> and not flushed
607    * for every edit.
608    * <p>
609    * NOTE:- This option might result in data loss if the region server crashes
610    * before these deferred edits in memory are flushed onto the filesystem.
611    * </p>
612    *
613    * @param isDeferredLogFlush
614    */
615   public synchronized void setDeferredLogFlush(final boolean isDeferredLogFlush) {
616     setValue(DEFERRED_LOG_FLUSH_KEY, isDeferredLogFlush? TRUE: FALSE);
617     this.deferredLog = isDeferredLogFlush;
618   }
619 
620   /**
621    * Get the name of the table as a byte array.
622    *
623    * @return name of table
624    */
625   public byte [] getName() {
626     return name;
627   }
628 
629   /**
630    * Get the name of the table as a String
631    *
632    * @return name of table as a String
633    */
634   public String getNameAsString() {
635     return this.nameAsString;
636   }
637 
638   /**
639    * This get the class associated with the region split policy which
640    * determines when a region split should occur.  The class used by
641    * default is defined in {@link org.apache.hadoop.hbase.regionserver.RegionSplitPolicy}
642    *
643    * @return the class name of the region split policy for this table.
644    * If this returns null, the default split policy is used.
645    */
646    public String getRegionSplitPolicyClassName() {
647     return getValue(SPLIT_POLICY);
648   }
649 
650   /**
651    * Set the name of the table.
652    *
653    * @param name name of table
654    */
655   public void setName(byte[] name) {
656     this.name = name;
657     this.nameAsString = Bytes.toString(this.name);
658     setMetaFlags(this.name);
659   }
660 
661   /**
662    * Returns the maximum size upto which a region can grow to after which a region
663    * split is triggered. The region size is represented by the size of the biggest
664    * store file in that region.
665    *
666    * @return max hregion size for table, -1 if not set.
667    *
668    * @see #setMaxFileSize(long)
669    */
670   public long getMaxFileSize() {
671     byte [] value = getValue(MAX_FILESIZE_KEY);
672     if (value != null) {
673       return Long.parseLong(Bytes.toString(value));
674     }
675     return -1;
676   }
677 
678   /**
679    * Sets the maximum size upto which a region can grow to after which a region
680    * split is triggered. The region size is represented by the size of the biggest
681    * store file in that region, i.e. If the biggest store file grows beyond the
682    * maxFileSize, then the region split is triggered. This defaults to a value of
683    * 256 MB.
684    * <p>
685    * This is not an absolute value and might vary. Assume that a single row exceeds
686    * the maxFileSize then the storeFileSize will be greater than maxFileSize since
687    * a single row cannot be split across multiple regions
688    * </p>
689    *
690    * @param maxFileSize The maximum file size that a store file can grow to
691    * before a split is triggered.
692    */
693   public void setMaxFileSize(long maxFileSize) {
694     setValue(MAX_FILESIZE_KEY, Long.toString(maxFileSize));
695   }
696 
697   /**
698    * Returns the size of the memstore after which a flush to filesystem is triggered.
699    *
700    * @return memory cache flush size for each hregion, -1 if not set.
701    *
702    * @see #setMemStoreFlushSize(long)
703    */
704   public long getMemStoreFlushSize() {
705     byte [] value = getValue(MEMSTORE_FLUSHSIZE_KEY);
706     if (value != null) {
707       return Long.parseLong(Bytes.toString(value));
708     }
709     return -1;
710   }
711 
712   /**
713    * Represents the maximum size of the memstore after which the contents of the
714    * memstore are flushed to the filesystem. This defaults to a size of 64 MB.
715    *
716    * @param memstoreFlushSize memory cache flush size for each hregion
717    */
718   public void setMemStoreFlushSize(long memstoreFlushSize) {
719     setValue(MEMSTORE_FLUSHSIZE_KEY, Long.toString(memstoreFlushSize));
720   }
721 
722   /**
723    * Adds a column family.
724    * @param family HColumnDescriptor of family to add.
725    */
726   public void addFamily(final HColumnDescriptor family) {
727     if (family.getName() == null || family.getName().length <= 0) {
728       throw new NullPointerException("Family name cannot be null or empty");
729     }
730     this.families.put(family.getName(), family);
731   }
732 
733   /**
734    * Checks to see if this table contains the given column family
735    * @param familyName Family name or column name.
736    * @return true if the table contains the specified family name
737    */
738   public boolean hasFamily(final byte [] familyName) {
739     return families.containsKey(familyName);
740   }
741 
742   /**
743    * @return Name of this table and then a map of all of the column family
744    * descriptors.
745    * @see #getNameAsString()
746    */
747   @Override
748   public String toString() {
749     StringBuilder s = new StringBuilder();
750     s.append('\'').append(Bytes.toString(name)).append('\'');
751     s.append(getValues(true));
752     for (HColumnDescriptor f : families.values()) {
753       s.append(", ").append(f);
754     }
755     return s.toString();
756   }
757 
758   /**
759    * @return Name of this table and then a map of all of the column family
760    * descriptors (with only the non-default column family attributes)
761    */
762   public String toStringCustomizedValues() {
763     StringBuilder s = new StringBuilder();
764     s.append('\'').append(Bytes.toString(name)).append('\'');
765     s.append(getValues(false));
766     for(HColumnDescriptor hcd : families.values()) {
767       s.append(", ").append(hcd.toStringCustomizedValues());
768     }
769     return s.toString();
770   }
771 
772   private StringBuilder getValues(boolean printDefaults) {
773     StringBuilder s = new StringBuilder();
774 
775     // step 1: set partitioning and pruning
776     Set<ImmutableBytesWritable> reservedKeys = new TreeSet<ImmutableBytesWritable>();
777     Set<ImmutableBytesWritable> userKeys = new TreeSet<ImmutableBytesWritable>();
778     for (ImmutableBytesWritable k : values.keySet()) {
779       if (k == null || k.get() == null) continue;
780       String key = Bytes.toString(k.get());
781       // in this section, print out reserved keywords + coprocessor info
782       if (!RESERVED_KEYWORDS.contains(k) && !key.startsWith("coprocessor$")) {
783         userKeys.add(k);
784         continue;
785       }
786       // only print out IS_ROOT/IS_META if true
787       String value = Bytes.toString(values.get(k).get());
788       if (key.equalsIgnoreCase(IS_ROOT) || key.equalsIgnoreCase(IS_META)) {
789         if (Boolean.valueOf(value) == false) continue;
790       }
791       // see if a reserved key is a default value. may not want to print it out
792       if (printDefaults
793           || !DEFAULT_VALUES.containsKey(key)
794           || !DEFAULT_VALUES.get(key).equalsIgnoreCase(value)) {
795         reservedKeys.add(k);
796       }
797     }
798 
799     // early exit optimization
800     boolean hasAttributes = !reservedKeys.isEmpty() || !userKeys.isEmpty();
801     if (!hasAttributes && configuration.isEmpty()) return s;
802 
803     s.append(", {");
804     // step 2: printing attributes
805     if (hasAttributes) {
806       s.append("TABLE_ATTRIBUTES => {");
807 
808       // print all reserved keys first
809       boolean printCommaForAttr = false;
810       for (ImmutableBytesWritable k : reservedKeys) {
811         String key = Bytes.toString(k.get());
812         String value = Bytes.toString(values.get(k).get());
813         if (printCommaForAttr) s.append(", ");
814         printCommaForAttr = true;
815         s.append(key);
816         s.append(" => ");
817         s.append('\'').append(value).append('\'');
818       }
819 
820       if (!userKeys.isEmpty()) {
821         // print all non-reserved, advanced config keys as a separate subset
822         if (printCommaForAttr) s.append(", ");
823         printCommaForAttr = true;
824         s.append(HConstants.METADATA).append(" => ");
825         s.append("{");
826         boolean printCommaForCfg = false;
827         for (ImmutableBytesWritable k : userKeys) {
828           String key = Bytes.toString(k.get());
829           String value = Bytes.toString(values.get(k).get());
830           if (printCommaForCfg) s.append(", ");
831           printCommaForCfg = true;
832           s.append('\'').append(key).append('\'');
833           s.append(" => ");
834           s.append('\'').append(value).append('\'');
835         }
836         s.append("}");
837       }
838     }
839 
840     // step 3: printing all configuration:
841     if (!configuration.isEmpty()) {
842       if (hasAttributes) {
843         s.append(", ");
844       }
845       s.append(HConstants.CONFIGURATION).append(" => ");
846       s.append('{');
847       boolean printCommaForConfig = false;
848       for (Map.Entry<String, String> e : configuration.entrySet()) {
849         if (printCommaForConfig) s.append(", ");
850         printCommaForConfig = true;
851         s.append('\'').append(e.getKey()).append('\'');
852         s.append(" => ");
853         s.append('\'').append(e.getValue()).append('\'');
854       }
855       s.append("}");
856     }
857     s.append("}"); // end METHOD
858     return s;
859   }
860 
861   /**
862    * Compare the contents of the descriptor with another one passed as a parameter.
863    * Checks if the obj passed is an instance of HTableDescriptor, if yes then the
864    * contents of the descriptors are compared.
865    *
866    * @return true if the contents of the the two descriptors exactly match
867    *
868    * @see java.lang.Object#equals(java.lang.Object)
869    */
870   @Override
871   public boolean equals(Object obj) {
872     if (this == obj) {
873       return true;
874     }
875     if (obj == null) {
876       return false;
877     }
878     if (!(obj instanceof HTableDescriptor)) {
879       return false;
880     }
881     return compareTo((HTableDescriptor)obj) == 0;
882   }
883 
884   /**
885    * @see java.lang.Object#hashCode()
886    */
887   @Override
888   public int hashCode() {
889     int result = Bytes.hashCode(this.name);
890     result ^= Byte.valueOf(TABLE_DESCRIPTOR_VERSION).hashCode();
891     if (this.families != null && this.families.size() > 0) {
892       for (HColumnDescriptor e: this.families.values()) {
893         result ^= e.hashCode();
894       }
895     }
896     result ^= values.hashCode();
897     result ^= configuration.hashCode();
898     return result;
899   }
900 
901   /**
902    * <em> INTERNAL </em> This method is a part of {@link WritableComparable} interface
903    * and is used for de-serialization of the HTableDescriptor over RPC
904    * @deprecated Writables are going away.  Use pb {@link #parseFrom(byte[])} instead.
905    */
906   @Deprecated
907   @Override
908   public void readFields(DataInput in) throws IOException {
909     int version = in.readInt();
910     if (version < 3)
911       throw new IOException("versions < 3 are not supported (and never existed!?)");
912     // version 3+
913     name = Bytes.readByteArray(in);
914     nameAsString = Bytes.toString(this.name);
915     setRootRegion(in.readBoolean());
916     setMetaRegion(in.readBoolean());
917     values.clear();
918     configuration.clear();
919     int numVals = in.readInt();
920     for (int i = 0; i < numVals; i++) {
921       ImmutableBytesWritable key = new ImmutableBytesWritable();
922       ImmutableBytesWritable value = new ImmutableBytesWritable();
923       key.readFields(in);
924       value.readFields(in);
925       setValue(key, value);
926     }
927     families.clear();
928     int numFamilies = in.readInt();
929     for (int i = 0; i < numFamilies; i++) {
930       HColumnDescriptor c = new HColumnDescriptor();
931       c.readFields(in);
932       families.put(c.getName(), c);
933     }
934     if (version >= 7) {
935       int numConfigs = in.readInt();
936       for (int i = 0; i < numConfigs; i++) {
937         ImmutableBytesWritable key = new ImmutableBytesWritable();
938         ImmutableBytesWritable value = new ImmutableBytesWritable();
939         key.readFields(in);
940         value.readFields(in);
941         configuration.put(
942           Bytes.toString(key.get(), key.getOffset(), key.getLength()),
943           Bytes.toString(value.get(), value.getOffset(), value.getLength()));
944       }
945     }
946   }
947 
948   /**
949    * <em> INTERNAL </em> This method is a part of {@link WritableComparable} interface
950    * and is used for serialization of the HTableDescriptor over RPC
951    * @deprecated Writables are going away.
952    * Use {@link com.google.protobuf.MessageLite#toByteArray} instead.
953    */
954   @Deprecated
955   @Override
956   public void write(DataOutput out) throws IOException {
957 	out.writeInt(TABLE_DESCRIPTOR_VERSION);
958     Bytes.writeByteArray(out, name);
959     out.writeBoolean(isRootRegion());
960     out.writeBoolean(isMetaRegion());
961     out.writeInt(values.size());
962     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
963         values.entrySet()) {
964       e.getKey().write(out);
965       e.getValue().write(out);
966     }
967     out.writeInt(families.size());
968     for(Iterator<HColumnDescriptor> it = families.values().iterator();
969         it.hasNext(); ) {
970       HColumnDescriptor family = it.next();
971       family.write(out);
972     }
973     out.writeInt(configuration.size());
974     for (Map.Entry<String, String> e : configuration.entrySet()) {
975       new ImmutableBytesWritable(Bytes.toBytes(e.getKey())).write(out);
976       new ImmutableBytesWritable(Bytes.toBytes(e.getValue())).write(out);
977     }
978   }
979 
980   // Comparable
981 
982   /**
983    * Compares the descriptor with another descriptor which is passed as a parameter.
984    * This compares the content of the two descriptors and not the reference.
985    *
986    * @return 0 if the contents of the descriptors are exactly matching,
987    * 		 1 if there is a mismatch in the contents
988    */
989   @Override
990   public int compareTo(final HTableDescriptor other) {
991     int result = Bytes.compareTo(this.name, other.name);
992     if (result == 0) {
993       result = families.size() - other.families.size();
994     }
995     if (result == 0 && families.size() != other.families.size()) {
996       result = Integer.valueOf(families.size()).compareTo(
997           Integer.valueOf(other.families.size()));
998     }
999     if (result == 0) {
1000       for (Iterator<HColumnDescriptor> it = families.values().iterator(),
1001           it2 = other.families.values().iterator(); it.hasNext(); ) {
1002         result = it.next().compareTo(it2.next());
1003         if (result != 0) {
1004           break;
1005         }
1006       }
1007     }
1008     if (result == 0) {
1009       // punt on comparison for ordering, just calculate difference
1010       result = this.values.hashCode() - other.values.hashCode();
1011       if (result < 0)
1012         result = -1;
1013       else if (result > 0)
1014         result = 1;
1015     }
1016     if (result == 0) {
1017       result = this.configuration.hashCode() - other.configuration.hashCode();
1018       if (result < 0)
1019         result = -1;
1020       else if (result > 0)
1021         result = 1;
1022     }
1023     return result;
1024   }
1025 
1026   /**
1027    * Returns an unmodifiable collection of all the {@link HColumnDescriptor}
1028    * of all the column families of the table.
1029    *
1030    * @return Immutable collection of {@link HColumnDescriptor} of all the
1031    * column families.
1032    */
1033   public Collection<HColumnDescriptor> getFamilies() {
1034     return Collections.unmodifiableCollection(this.families.values());
1035   }
1036 
1037   /**
1038    * Returns all the column family names of the current table. The map of
1039    * HTableDescriptor contains mapping of family name to HColumnDescriptors.
1040    * This returns all the keys of the family map which represents the column
1041    * family names of the table.
1042    *
1043    * @return Immutable sorted set of the keys of the families.
1044    */
1045   public Set<byte[]> getFamiliesKeys() {
1046     return Collections.unmodifiableSet(this.families.keySet());
1047   }
1048 
1049   /**
1050    * Returns an array all the {@link HColumnDescriptor} of the column families
1051    * of the table.
1052    *
1053    * @return Array of all the HColumnDescriptors of the current table
1054    *
1055    * @see #getFamilies()
1056    */
1057   public HColumnDescriptor[] getColumnFamilies() {
1058     Collection<HColumnDescriptor> hColumnDescriptors = getFamilies();
1059     return hColumnDescriptors.toArray(new HColumnDescriptor[hColumnDescriptors.size()]);
1060   }
1061 
1062 
1063   /**
1064    * Returns the HColumnDescriptor for a specific column family with name as
1065    * specified by the parameter column.
1066    *
1067    * @param column Column family name
1068    * @return Column descriptor for the passed family name or the family on
1069    * passed in column.
1070    */
1071   public HColumnDescriptor getFamily(final byte [] column) {
1072     return this.families.get(column);
1073   }
1074 
1075 
1076   /**
1077    * Removes the HColumnDescriptor with name specified by the parameter column
1078    * from the table descriptor
1079    *
1080    * @param column Name of the column family to be removed.
1081    * @return Column descriptor for the passed family name or the family on
1082    * passed in column.
1083    */
1084   public HColumnDescriptor removeFamily(final byte [] column) {
1085     return this.families.remove(column);
1086   }
1087 
1088 
1089   /**
1090    * Add a table coprocessor to this table. The coprocessor
1091    * type must be {@link org.apache.hadoop.hbase.coprocessor.RegionObserver}
1092    * or Endpoint.
1093    * It won't check if the class can be loaded or not.
1094    * Whether a coprocessor is loadable or not will be determined when
1095    * a region is opened.
1096    * @param className Full class name.
1097    * @throws IOException
1098    */
1099   public void addCoprocessor(String className) throws IOException {
1100     addCoprocessor(className, null, Coprocessor.PRIORITY_USER, null);
1101   }
1102 
1103 
1104   /**
1105    * Add a table coprocessor to this table. The coprocessor
1106    * type must be {@link org.apache.hadoop.hbase.coprocessor.RegionObserver}
1107    * or Endpoint.
1108    * It won't check if the class can be loaded or not.
1109    * Whether a coprocessor is loadable or not will be determined when
1110    * a region is opened.
1111    * @param jarFilePath Path of the jar file. If it's null, the class will be
1112    * loaded from default classloader.
1113    * @param className Full class name.
1114    * @param priority Priority
1115    * @param kvs Arbitrary key-value parameter pairs passed into the coprocessor.
1116    * @throws IOException
1117    */
1118   public void addCoprocessor(String className, Path jarFilePath,
1119                              int priority, final Map<String, String> kvs)
1120   throws IOException {
1121     if (hasCoprocessor(className)) {
1122       throw new IOException("Coprocessor " + className + " already exists.");
1123     }
1124     // validate parameter kvs
1125     StringBuilder kvString = new StringBuilder();
1126     if (kvs != null) {
1127       for (Map.Entry<String, String> e: kvs.entrySet()) {
1128         if (!e.getKey().matches(HConstants.CP_HTD_ATTR_VALUE_PARAM_KEY_PATTERN)) {
1129           throw new IOException("Illegal parameter key = " + e.getKey());
1130         }
1131         if (!e.getValue().matches(HConstants.CP_HTD_ATTR_VALUE_PARAM_VALUE_PATTERN)) {
1132           throw new IOException("Illegal parameter (" + e.getKey() +
1133               ") value = " + e.getValue());
1134         }
1135         if (kvString.length() != 0) {
1136           kvString.append(',');
1137         }
1138         kvString.append(e.getKey());
1139         kvString.append('=');
1140         kvString.append(e.getValue());
1141       }
1142     }
1143 
1144     // generate a coprocessor key
1145     int maxCoprocessorNumber = 0;
1146     Matcher keyMatcher;
1147     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
1148         this.values.entrySet()) {
1149       keyMatcher =
1150           HConstants.CP_HTD_ATTR_KEY_PATTERN.matcher(
1151               Bytes.toString(e.getKey().get()));
1152       if (!keyMatcher.matches()) {
1153         continue;
1154       }
1155       maxCoprocessorNumber = Math.max(Integer.parseInt(keyMatcher.group(1)),
1156           maxCoprocessorNumber);
1157     }
1158     maxCoprocessorNumber++;
1159 
1160     String key = "coprocessor$" + Integer.toString(maxCoprocessorNumber);
1161     String value = ((jarFilePath == null)? "" : jarFilePath.toString()) +
1162         "|" + className + "|" + Integer.toString(priority) + "|" +
1163         kvString.toString();
1164     setValue(key, value);
1165   }
1166 
1167 
1168   /**
1169    * Check if the table has an attached co-processor represented by the name className
1170    *
1171    * @param className - Class name of the co-processor
1172    * @return true of the table has a co-processor className
1173    */
1174   public boolean hasCoprocessor(String className) {
1175     Matcher keyMatcher;
1176     Matcher valueMatcher;
1177     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
1178         this.values.entrySet()) {
1179       keyMatcher =
1180           HConstants.CP_HTD_ATTR_KEY_PATTERN.matcher(
1181               Bytes.toString(e.getKey().get()));
1182       if (!keyMatcher.matches()) {
1183         continue;
1184       }
1185       valueMatcher =
1186         HConstants.CP_HTD_ATTR_VALUE_PATTERN.matcher(
1187             Bytes.toString(e.getValue().get()));
1188       if (!valueMatcher.matches()) {
1189         continue;
1190       }
1191       // get className and compare
1192       String clazz = valueMatcher.group(2).trim(); // classname is the 2nd field
1193       if (clazz.equals(className.trim())) {
1194         return true;
1195       }
1196     }
1197     return false;
1198   }
1199 
1200   /**
1201    * Return the list of attached co-processor represented by their name className
1202    *
1203    * @return The list of co-processors classNames
1204    */
1205   public List<String> getCoprocessors() {
1206     List<String> result = new ArrayList<String>();
1207     Matcher keyMatcher;
1208     Matcher valueMatcher;
1209     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e : this.values.entrySet()) {
1210       keyMatcher = HConstants.CP_HTD_ATTR_KEY_PATTERN.matcher(Bytes.toString(e.getKey().get()));
1211       if (!keyMatcher.matches()) {
1212         continue;
1213       }
1214       valueMatcher = HConstants.CP_HTD_ATTR_VALUE_PATTERN.matcher(Bytes
1215           .toString(e.getValue().get()));
1216       if (!valueMatcher.matches()) {
1217         continue;
1218       }
1219       result.add(valueMatcher.group(2).trim()); // classname is the 2nd field
1220     }
1221     return result;
1222   }
1223 
1224   /**
1225    * Remove a coprocessor from those set on the table
1226    * @param className Class name of the co-processor
1227    */
1228   public void removeCoprocessor(String className) {
1229     ImmutableBytesWritable match = null;
1230     Matcher keyMatcher;
1231     Matcher valueMatcher;
1232     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e : this.values
1233         .entrySet()) {
1234       keyMatcher = HConstants.CP_HTD_ATTR_KEY_PATTERN.matcher(Bytes.toString(e
1235           .getKey().get()));
1236       if (!keyMatcher.matches()) {
1237         continue;
1238       }
1239       valueMatcher = HConstants.CP_HTD_ATTR_VALUE_PATTERN.matcher(Bytes
1240           .toString(e.getValue().get()));
1241       if (!valueMatcher.matches()) {
1242         continue;
1243       }
1244       // get className and compare
1245       String clazz = valueMatcher.group(2).trim(); // classname is the 2nd field
1246       // remove the CP if it is present
1247       if (clazz.equals(className.trim())) {
1248         match = e.getKey();
1249         break;
1250       }
1251     }
1252     // if we found a match, remove it
1253     if (match != null)
1254       remove(match);
1255   }
1256 
1257   /**
1258    * Returns the {@link Path} object representing the table directory under
1259    * path rootdir
1260    *
1261    * @param rootdir qualified path of HBase root directory
1262    * @param tableName name of table
1263    * @return {@link Path} for table
1264    */
1265   public static Path getTableDir(Path rootdir, final byte [] tableName) {
1266     return new Path(rootdir, Bytes.toString(tableName));
1267   }
1268 
1269   /** Table descriptor for <core>-ROOT-</code> catalog table */
1270   public static final HTableDescriptor ROOT_TABLEDESC = new HTableDescriptor(
1271       HConstants.ROOT_TABLE_NAME,
1272       new HColumnDescriptor[] {
1273           new HColumnDescriptor(HConstants.CATALOG_FAMILY)
1274               // Ten is arbitrary number.  Keep versions to help debugging.
1275               .setMaxVersions(10)
1276               .setInMemory(true)
1277               .setBlocksize(8 * 1024)
1278               .setTimeToLive(HConstants.FOREVER)
1279               .setScope(HConstants.REPLICATION_SCOPE_LOCAL)
1280       });
1281 
1282   /** Table descriptor for <code>.META.</code> catalog table */
1283   public static final HTableDescriptor META_TABLEDESC = new HTableDescriptor(
1284       HConstants.META_TABLE_NAME, new HColumnDescriptor[] {
1285           new HColumnDescriptor(HConstants.CATALOG_FAMILY)
1286               // Ten is arbitrary number.  Keep versions to help debugging.
1287               .setMaxVersions(10)
1288               .setInMemory(true)
1289               .setBlocksize(8 * 1024)
1290               .setScope(HConstants.REPLICATION_SCOPE_LOCAL)
1291               // Disable blooms for meta.  Needs work.  Seems to mess w/ getClosestOrBefore.
1292               .setBloomFilterType(BloomType.NONE)
1293       });
1294 
1295   static {
1296     try {
1297       META_TABLEDESC.addCoprocessor(
1298           "org.apache.hadoop.hbase.coprocessor.MultiRowMutationEndpoint",
1299           null, Coprocessor.PRIORITY_SYSTEM, null);
1300     } catch (IOException ex) {
1301       //LOG.warn("exception in loading coprocessor for the META table");
1302       throw new RuntimeException(ex);
1303     }
1304   }
1305 
1306 
1307   @Deprecated
1308   public void setOwner(User owner) {
1309     setOwnerString(owner != null ? owner.getShortName() : null);
1310   }
1311 
1312   // used by admin.rb:alter(table_name,*args) to update owner.
1313   @Deprecated
1314   public void setOwnerString(String ownerString) {
1315     if (ownerString != null) {
1316       setValue(OWNER_KEY, ownerString);
1317     } else {
1318       remove(OWNER_KEY);
1319     }
1320   }
1321 
1322   @Deprecated
1323   public String getOwnerString() {
1324     if (getValue(OWNER_KEY) != null) {
1325       return Bytes.toString(getValue(OWNER_KEY));
1326     }
1327     // Note that every table should have an owner (i.e. should have OWNER_KEY set).
1328     // .META. and -ROOT- should return system user as owner, not null (see
1329     // MasterFileSystem.java:bootstrap()).
1330     return null;
1331   }
1332 
1333   /**
1334    * @return This instance serialized with pb with pb magic prefix
1335    * @see #parseFrom(byte[])
1336    */
1337   public byte [] toByteArray() {
1338     return ProtobufUtil.prependPBMagic(convert().toByteArray());
1339   }
1340 
1341   /**
1342    * @param bytes A pb serialized {@link HTableDescriptor} instance with pb magic prefix
1343    * @return An instance of {@link HTableDescriptor} made from <code>bytes</code>
1344    * @throws DeserializationException
1345    * @throws IOException
1346    * @see #toByteArray()
1347    */
1348   public static HTableDescriptor parseFrom(final byte [] bytes)
1349   throws DeserializationException, IOException {
1350     if (!ProtobufUtil.isPBMagicPrefix(bytes)) {
1351       return (HTableDescriptor)Writables.getWritable(bytes, new HTableDescriptor());
1352     }
1353     int pblen = ProtobufUtil.lengthOfPBMagic();
1354     TableSchema.Builder builder = TableSchema.newBuilder();
1355     TableSchema ts;
1356     try {
1357       ts = builder.mergeFrom(bytes, pblen, bytes.length - pblen).build();
1358     } catch (InvalidProtocolBufferException e) {
1359       throw new DeserializationException(e);
1360     }
1361     return convert(ts);
1362   }
1363 
1364   /**
1365    * @return Convert the current {@link HTableDescriptor} into a pb TableSchema instance.
1366    */
1367   public TableSchema convert() {
1368     TableSchema.Builder builder = TableSchema.newBuilder();
1369     builder.setName(ByteString.copyFrom(getName()));
1370     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e: this.values.entrySet()) {
1371       BytesBytesPair.Builder aBuilder = BytesBytesPair.newBuilder();
1372       aBuilder.setFirst(ByteString.copyFrom(e.getKey().get()));
1373       aBuilder.setSecond(ByteString.copyFrom(e.getValue().get()));
1374       builder.addAttributes(aBuilder.build());
1375     }
1376     for (HColumnDescriptor hcd: getColumnFamilies()) {
1377       builder.addColumnFamilies(hcd.convert());
1378     }
1379     for (Map.Entry<String, String> e : this.configuration.entrySet()) {
1380       NameStringPair.Builder aBuilder = NameStringPair.newBuilder();
1381       aBuilder.setName(e.getKey());
1382       aBuilder.setValue(e.getValue());
1383       builder.addConfiguration(aBuilder.build());
1384     }
1385     return builder.build();
1386   }
1387 
1388   /**
1389    * @param ts A pb TableSchema instance.
1390    * @return An {@link HTableDescriptor} made from the passed in pb <code>ts</code>.
1391    */
1392   public static HTableDescriptor convert(final TableSchema ts) {
1393     List<ColumnFamilySchema> list = ts.getColumnFamiliesList();
1394     HColumnDescriptor [] hcds = new HColumnDescriptor[list.size()];
1395     int index = 0;
1396     for (ColumnFamilySchema cfs: list) {
1397       hcds[index++] = HColumnDescriptor.convert(cfs);
1398     }
1399     HTableDescriptor htd = new HTableDescriptor(ts.getName().toByteArray(), hcds);
1400     for (BytesBytesPair a: ts.getAttributesList()) {
1401       htd.setValue(a.getFirst().toByteArray(), a.getSecond().toByteArray());
1402     }
1403     for (NameStringPair a: ts.getConfigurationList()) {
1404       htd.setConfiguration(a.getName(), a.getValue());
1405     }
1406     return htd;
1407   }
1408 
1409   /**
1410    * Getter for accessing the configuration value by key
1411    */
1412   public String getConfigurationValue(String key) {
1413     return configuration.get(key);
1414   }
1415 
1416   /**
1417    * Getter for fetching an unmodifiable {@link #configuration} map.
1418    */
1419   public Map<String, String> getConfiguration() {
1420     // shallow pointer copy
1421     return Collections.unmodifiableMap(configuration);
1422   }
1423 
1424   /**
1425    * Setter for storing a configuration setting in {@link #configuration} map.
1426    * @param key Config key. Same as XML config key e.g. hbase.something.or.other.
1427    * @param value String value. If null, removes the setting.
1428    */
1429   public void setConfiguration(String key, String value) {
1430     if (value == null) {
1431       removeConfiguration(key);
1432     } else {
1433       configuration.put(key, value);
1434     }
1435   }
1436 
1437   /**
1438    * Remove a config setting represented by the key from the {@link #configuration} map
1439    */
1440   public void removeConfiguration(final String key) {
1441     configuration.remove(key);
1442   }
1443 }