View Javadoc

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