View Javadoc

1   /**
2    * Copyright 2009 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase;
21  
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TreeMap;
32  import java.util.regex.Matcher;
33  
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
36  import org.apache.hadoop.hbase.io.hfile.Compression;
37  import org.apache.hadoop.hbase.regionserver.StoreFile;
38  import org.apache.hadoop.hbase.security.User;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.io.WritableComparable;
41  
42  /**
43   * HTableDescriptor contains the details about an HBase table  such as the descriptors of
44   * all the column families, is the table a catalog table, <code> -ROOT- </code> or 
45   * <code> .META. </code>, is the table is read only, the maximum size of the memstore, 
46   * when the region split should occur, coprocessors associated with it etc...
47   */
48  public class HTableDescriptor implements WritableComparable<HTableDescriptor> {
49  
50    /**
51     *  Changes prior to version 3 were not recorded here.
52     *  Version 3 adds metadata as a map where keys and values are byte[].
53     *  Version 4 adds indexes
54     *  Version 5 removed transactional pollution -- e.g. indexes
55     */
56    private static final byte TABLE_DESCRIPTOR_VERSION = 5;
57  
58    private byte [] name = HConstants.EMPTY_BYTE_ARRAY;
59  
60    private String nameAsString = "";
61  
62    /**
63     * A map which holds the metadata information of the table. This metadata 
64     * includes values like IS_ROOT, IS_META, DEFERRED_LOG_FLUSH, SPLIT_POLICY,
65     * MAX_FILE_SIZE, READONLY, MEMSTORE_FLUSHSIZE etc...
66     */
67    protected Map<ImmutableBytesWritable, ImmutableBytesWritable> values =
68      new HashMap<ImmutableBytesWritable, ImmutableBytesWritable>();
69  
70    private static final String FAMILIES = "FAMILIES";
71  
72    private static final String SPLIT_POLICY = "SPLIT_POLICY";
73    
74    /**
75     * <em>INTERNAL</em> Used by HBase Shell interface to access this metadata 
76     * attribute which denotes the maximum size of the store file after which 
77     * a region split occurs
78     * 
79     * @see #getMaxFileSize()
80     */
81    public static final String MAX_FILESIZE = "MAX_FILESIZE";
82    private static final ImmutableBytesWritable MAX_FILESIZE_KEY =
83      new ImmutableBytesWritable(Bytes.toBytes(MAX_FILESIZE));
84  
85    public static final String OWNER = "OWNER";
86    public static final ImmutableBytesWritable OWNER_KEY =
87      new ImmutableBytesWritable(Bytes.toBytes(OWNER));
88  
89    /**
90     * <em>INTERNAL</em> Used by rest interface to access this metadata 
91     * attribute which denotes if the table is Read Only
92     * 
93     * @see #isReadOnly()
94     */
95    public static final String READONLY = "READONLY";
96    private static final ImmutableBytesWritable READONLY_KEY =
97      new ImmutableBytesWritable(Bytes.toBytes(READONLY));
98  
99    /**
100    * <em>INTERNAL</em> Used by HBase Shell interface to access this metadata 
101    * attribute which represents the maximum size of the memstore after which 
102    * its contents are flushed onto the disk
103    * 
104    * @see #getMemStoreFlushSize()
105    */
106   public static final String MEMSTORE_FLUSHSIZE = "MEMSTORE_FLUSHSIZE";
107   private static final ImmutableBytesWritable MEMSTORE_FLUSHSIZE_KEY =
108     new ImmutableBytesWritable(Bytes.toBytes(MEMSTORE_FLUSHSIZE));
109 
110   /**
111    * <em>INTERNAL</em> Used by rest interface to access this metadata 
112    * attribute which denotes if the table is a -ROOT- region or not
113    * 
114    * @see #isRootRegion()
115    */
116   public static final String IS_ROOT = "IS_ROOT";
117   private static final ImmutableBytesWritable IS_ROOT_KEY =
118     new ImmutableBytesWritable(Bytes.toBytes(IS_ROOT));
119 
120   /**
121    * <em>INTERNAL</em> Used by rest interface to access this metadata 
122    * attribute which denotes if it is a catalog table, either
123    * <code> .META. </code> or <code> -ROOT- </code>
124    * 
125    * @see #isMetaRegion()
126    */
127   public static final String IS_META = "IS_META";
128   private static final ImmutableBytesWritable IS_META_KEY =
129     new ImmutableBytesWritable(Bytes.toBytes(IS_META));
130 
131   /**
132    * <em>INTERNAL</em> Used by HBase Shell interface to access this metadata 
133    * attribute which denotes if the deferred log flush option is enabled
134    */
135   public static final String DEFERRED_LOG_FLUSH = "DEFERRED_LOG_FLUSH";
136   private static final ImmutableBytesWritable DEFERRED_LOG_FLUSH_KEY =
137     new ImmutableBytesWritable(Bytes.toBytes(DEFERRED_LOG_FLUSH));
138 
139   /*
140    *  The below are ugly but better than creating them each time till we
141    *  replace booleans being saved as Strings with plain booleans.  Need a
142    *  migration script to do this.  TODO.
143    */
144   private static final ImmutableBytesWritable FALSE =
145     new ImmutableBytesWritable(Bytes.toBytes(Boolean.FALSE.toString()));
146 
147   private static final ImmutableBytesWritable TRUE =
148     new ImmutableBytesWritable(Bytes.toBytes(Boolean.TRUE.toString()));
149 
150   private static final boolean DEFAULT_DEFERRED_LOG_FLUSH = false;
151   
152   /**
153    * Constant that denotes whether the table is READONLY by default and is false
154    */
155   public static final boolean DEFAULT_READONLY = false;
156 
157   /**
158    * Constant that denotes the maximum default size of the memstore after which 
159    * the contents are flushed to the store files
160    */
161   public static final long DEFAULT_MEMSTORE_FLUSH_SIZE = 1024*1024*64L;
162 
163   private volatile Boolean meta = null;
164   private volatile Boolean root = null;
165   private Boolean isDeferredLog = null;
166 
167   /**
168    * Maps column family name to the respective HColumnDescriptors
169    */
170   private final Map<byte [], HColumnDescriptor> families =
171     new TreeMap<byte [], HColumnDescriptor>(Bytes.BYTES_RAWCOMPARATOR);
172 
173   /**
174    * <em> INTERNAL </em> Private constructor used internally creating table descriptors for
175    * catalog tables, <code>.META.</code> and <code>-ROOT-</code>.
176    */
177   protected HTableDescriptor(final byte [] name, HColumnDescriptor[] families) {
178     this.name = name.clone();
179     this.nameAsString = Bytes.toString(this.name);
180     setMetaFlags(name);
181     for(HColumnDescriptor descriptor : families) {
182       this.families.put(descriptor.getName(), descriptor);
183     }
184   }
185 
186   /**
187    * <em> INTERNAL </em>Private constructor used internally creating table descriptors for
188    * catalog tables, <code>.META.</code> and <code>-ROOT-</code>.
189    */
190   protected HTableDescriptor(final byte [] name, HColumnDescriptor[] families,
191       Map<ImmutableBytesWritable,ImmutableBytesWritable> values) {
192     this.name = name.clone();
193     this.nameAsString = Bytes.toString(this.name);
194     setMetaFlags(name);
195     for(HColumnDescriptor descriptor : families) {
196       this.families.put(descriptor.getName(), descriptor);
197     }
198     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> entry:
199         values.entrySet()) {
200       this.values.put(entry.getKey(), entry.getValue());
201     }
202   }
203 
204   /**
205    * Default constructor which constructs an empty object.
206    * For deserializing an HTableDescriptor instance only.
207    * @see #HTableDescriptor(byte[])
208    */
209   public HTableDescriptor() {
210     super();
211   }
212 
213   /**
214    * Construct a table descriptor specifying table name.
215    * @param name Table name.
216    * @throws IllegalArgumentException if passed a table name
217    * that is made of other than 'word' characters, underscore or period: i.e.
218    * <code>[a-zA-Z_0-9.].
219    * @see <a href="HADOOP-1581">HADOOP-1581 HBASE: Un-openable tablename bug</a>
220    */
221   public HTableDescriptor(final String name) {
222     this(Bytes.toBytes(name));
223   }
224 
225   /**
226    * Construct a table descriptor specifying a byte array table name
227    * @param name - Table name as a byte array.
228    * @throws IllegalArgumentException if passed a table name
229    * that is made of other than 'word' characters, underscore or period: i.e.
230    * <code>[a-zA-Z_0-9-.].
231    * @see <a href="HADOOP-1581">HADOOP-1581 HBASE: Un-openable tablename bug</a>
232    */
233   public HTableDescriptor(final byte [] name) {
234     super();
235     setMetaFlags(this.name);
236     this.name = this.isMetaRegion()? name: isLegalTableName(name);
237     this.nameAsString = Bytes.toString(this.name);
238   }
239 
240   /**
241    * Construct a table descriptor by cloning the descriptor passed as a parameter.
242    * <p>
243    * Makes a deep copy of the supplied descriptor.
244    * Can make a modifiable descriptor from an UnmodifyableHTableDescriptor.
245    * @param desc The descriptor.
246    */
247   public HTableDescriptor(final HTableDescriptor desc) {
248     super();
249     this.name = desc.name.clone();
250     this.nameAsString = Bytes.toString(this.name);
251     setMetaFlags(this.name);
252     for (HColumnDescriptor c: desc.families.values()) {
253       this.families.put(c.getName(), new HColumnDescriptor(c));
254     }
255     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
256         desc.values.entrySet()) {
257       this.values.put(e.getKey(), e.getValue());
258     }
259   }
260 
261   /*
262    * Set meta flags on this table. 
263    * IS_ROOT_KEY is set if its a -ROOT- table
264    * IS_META_KEY is set either if its a -ROOT- or a .META. table 
265    * Called by constructors.
266    * @param name
267    */
268   private void setMetaFlags(final byte [] name) {
269     setRootRegion(Bytes.equals(name, HConstants.ROOT_TABLE_NAME));
270     setMetaRegion(isRootRegion() ||
271       Bytes.equals(name, HConstants.META_TABLE_NAME));
272   }
273 
274   /**
275    * Check if the descriptor represents a <code> -ROOT- </code> region.
276    * 
277    * @return true if this is a <code> -ROOT- </code> region 
278    */
279   public boolean isRootRegion() {
280     if (this.root == null) {
281       this.root = isSomething(IS_ROOT_KEY, false)? Boolean.TRUE: Boolean.FALSE;
282     }
283     return this.root.booleanValue();
284   }
285 
286   /**
287    * <em> INTERNAL </em> Used to denote if the current table represents 
288    * <code> -ROOT- </code> region. This is used internally by the 
289    * HTableDescriptor constructors 
290    * 
291    * @param isRoot true if this is the <code> -ROOT- </code> region 
292    */
293   protected void setRootRegion(boolean isRoot) {
294     // TODO: Make the value a boolean rather than String of boolean.
295     values.put(IS_ROOT_KEY, isRoot? TRUE: FALSE);
296   }
297 
298   /**
299    * Checks if this table is either <code> -ROOT- </code> or <code> .META. </code>
300    * region. 
301    *  
302    * @return true if this is either a <code> -ROOT- </code> or <code> .META. </code> 
303    * region 
304    */
305   public boolean isMetaRegion() {
306     if (this.meta == null) {
307       this.meta = calculateIsMetaRegion();
308     }
309     return this.meta.booleanValue();
310   }
311 
312   private synchronized Boolean calculateIsMetaRegion() {
313     byte [] value = getValue(IS_META_KEY);
314     return (value != null)? Boolean.valueOf(Bytes.toString(value)): Boolean.FALSE;
315   }
316 
317   private boolean isSomething(final ImmutableBytesWritable key,
318       final boolean valueIfNull) {
319     byte [] value = getValue(key);
320     if (value != null) {
321       // TODO: Make value be a boolean rather than String of boolean.
322       return Boolean.valueOf(Bytes.toString(value)).booleanValue();
323     }
324     return valueIfNull;
325   }
326 
327   /**
328    * <em> INTERNAL </em> Used to denote if the current table represents 
329    * <code> -ROOT- </code> or <code> .META. </code> region. This is used 
330    * internally by the HTableDescriptor constructors 
331    * 
332    * @param isMeta true if its either <code> -ROOT- </code> or 
333    * <code> .META. </code> region 
334    */
335   protected void setMetaRegion(boolean isMeta) {
336     values.put(IS_META_KEY, isMeta? TRUE: FALSE);
337   }
338 
339   /** 
340    * Checks if the table is a <code>.META.</code> table 
341    *  
342    * @return true if table is <code> .META. </code> region.
343    */
344   public boolean isMetaTable() {
345     return isMetaRegion() && !isRootRegion();
346   }
347  
348   /**
349    * Checks of the tableName being passed represents either 
350    * <code > -ROOT- </code> or <code> .META. </code>
351    *  
352    * @return true if a tablesName is either <code> -ROOT- </code> 
353    * or <code> .META. </code>
354    */
355   public static boolean isMetaTable(final byte [] tableName) {
356     return Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME) ||
357       Bytes.equals(tableName, HConstants.META_TABLE_NAME);
358   }
359 
360   /**
361    * Check passed byte buffer, "tableName", is legal user-space table name.
362    * @return Returns passed <code>tableName</code> param
363    * @throws NullPointerException If passed <code>tableName</code> is null
364    * @throws IllegalArgumentException if passed a tableName
365    * that is made of other than 'word' characters or underscores: i.e.
366    * <code>[a-zA-Z_0-9].
367    */
368   public static byte [] isLegalTableName(final byte [] tableName) {
369     if (tableName == null || tableName.length <= 0) {
370       throw new IllegalArgumentException("Name is null or empty");
371     }
372     if (tableName[0] == '.' || tableName[0] == '-') {
373       throw new IllegalArgumentException("Illegal first character <" + tableName[0] +
374           "> at 0. User-space table names can only start with 'word " +
375           "characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(tableName));
376     }
377     for (int i = 0; i < tableName.length; i++) {
378       if (Character.isLetterOrDigit(tableName[i]) || tableName[i] == '_' || 
379     		  tableName[i] == '-' || tableName[i] == '.') {
380         continue;
381       }
382       throw new IllegalArgumentException("Illegal character <" + tableName[i] +
383         "> at " + i + ". User-space table names can only contain " +
384         "'word characters': i.e. [a-zA-Z_0-9-.]: " + Bytes.toString(tableName));
385     }
386     return tableName;
387   }
388 
389   /**
390    * Getter for accessing the metadata associated with the key
391    *  
392    * @param key The key.
393    * @return The value.
394    * @see #values
395    */
396   public byte[] getValue(byte[] key) {
397     return getValue(new ImmutableBytesWritable(key));
398   }
399 
400   private byte[] getValue(final ImmutableBytesWritable key) {
401     ImmutableBytesWritable ibw = values.get(key);
402     if (ibw == null)
403       return null;
404     return ibw.get();
405   }
406 
407   /**
408    * Getter for accessing the metadata associated with the key
409    *  
410    * @param key The key.
411    * @return The value.
412    * @see #values
413    */
414   public String getValue(String key) {
415     byte[] value = getValue(Bytes.toBytes(key));
416     if (value == null)
417       return null;
418     return Bytes.toString(value);
419   }
420 
421   /**
422    * Getter for fetching an unmodifiable {@link #values} map.
423    *  
424    * @return unmodifiable map {@link #values}.
425    * @see #values
426    */
427   public Map<ImmutableBytesWritable,ImmutableBytesWritable> getValues() {
428      return Collections.unmodifiableMap(values);
429   }
430 
431   /**
432    * Setter for storing metadata as a (key, value) pair in {@link #values} map
433    *  
434    * @param key The key.
435    * @param value The value.
436    * @see #values
437    */
438   public void setValue(byte[] key, byte[] value) {
439     setValue(new ImmutableBytesWritable(key), value);
440   }
441 
442   /*
443    * @param key The key.
444    * @param value The value.
445    */
446   private void setValue(final ImmutableBytesWritable key,
447       final byte[] value) {
448     values.put(key, new ImmutableBytesWritable(value));
449   }
450 
451   /*
452    * @param key The key.
453    * @param value The value.
454    */
455   private void setValue(final ImmutableBytesWritable key,
456       final ImmutableBytesWritable value) {
457     values.put(key, value);
458   }
459 
460   /**
461    * Setter for storing metadata as a (key, value) pair in {@link #values} map
462    *  
463    * @param key The key.
464    * @param value The value.
465    * @see #values
466    */
467   public void setValue(String key, String value) {
468     setValue(Bytes.toBytes(key), Bytes.toBytes(value));
469   }
470 
471   /**
472    * Remove metadata represented by the key from the {@link #values} map
473    * 
474    * @param key Key whose key and value we're to remove from HTableDescriptor
475    * parameters.
476    */
477   public void remove(final byte [] key) {
478     values.remove(new ImmutableBytesWritable(key));
479   }
480 
481   /**
482    * Check if the readOnly flag of the table is set. If the readOnly flag is 
483    * set then the contents of the table can only be read from but not modified.
484    * 
485    * @return true if all columns in the table should be read only
486    */
487   public boolean isReadOnly() {
488     return isSomething(READONLY_KEY, DEFAULT_READONLY);
489   }
490 
491   /**
492    * Setting the table as read only sets all the columns in the table as read
493    * only. By default all tables are modifiable, but if the readOnly flag is 
494    * set to true then the contents of the table can only be read but not modified.
495    *  
496    * @param readOnly True if all of the columns in the table should be read
497    * only.
498    */
499   public void setReadOnly(final boolean readOnly) {
500     setValue(READONLY_KEY, readOnly? TRUE: FALSE);
501   }
502 
503   /**
504    * Check if deferred log edits are enabled on the table.  
505    * 
506    * @return true if that deferred log flush is enabled on the table
507    * 
508    * @see #setDeferredLogFlush(boolean)
509    */
510   public synchronized boolean isDeferredLogFlush() {
511     if(this.isDeferredLog == null) {
512       this.isDeferredLog =
513           isSomething(DEFERRED_LOG_FLUSH_KEY, DEFAULT_DEFERRED_LOG_FLUSH);
514     }
515     return this.isDeferredLog;
516   }
517 
518   /**
519    * This is used to defer the log edits syncing to the file system. Everytime 
520    * an edit is sent to the server it is first sync'd to the file system by the 
521    * log writer. This sync is an expensive operation and thus can be deferred so 
522    * that the edits are kept in memory for a specified period of time as represented
523    * by <code> hbase.regionserver.optionallogflushinterval </code> and not flushed
524    * for every edit.
525    * <p>
526    * NOTE:- This option might result in data loss if the region server crashes
527    * before these deferred edits in memory are flushed onto the filesystem. 
528    * </p>
529    * 
530    * @param isDeferredLogFlush
531    */
532   public void setDeferredLogFlush(final boolean isDeferredLogFlush) {
533     setValue(DEFERRED_LOG_FLUSH_KEY, isDeferredLogFlush? TRUE: FALSE);
534     this.isDeferredLog = isDeferredLogFlush;
535   }
536 
537   /**
538    * Get the name of the table as a byte array.
539    * 
540    * @return name of table 
541    */
542   public byte [] getName() {
543     return name;
544   }
545 
546   /**
547    * Get the name of the table as a String
548    * 
549    * @return name of table as a String 
550    */
551   public String getNameAsString() {
552     return this.nameAsString;
553   }
554   
555   /**
556    * This get the class associated with the region split policy which 
557    * determines when a region split should occur.  The class used by
558    * default is {@link org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy}
559    * which split the region base on a constant {@link #getMaxFileSize()}
560    * 
561    * @return the class name of the region split policy for this table.
562    * If this returns null, the default constant size based split policy
563    * is used.
564    */
565    public String getRegionSplitPolicyClassName() {
566     return getValue(SPLIT_POLICY);
567   }
568 
569   /**
570    * Set the name of the table. 
571    * 
572    * @param name name of table 
573    */
574   public void setName(byte[] name) {
575     this.name = name;
576     this.nameAsString = Bytes.toString(this.name);
577     setMetaFlags(this.name);
578   }
579 
580   /** 
581    * Returns the maximum size upto which a region can grow to after which a region
582    * split is triggered. The region size is represented by the size of the biggest 
583    * store file in that region.
584    * 
585    * @return max hregion size for table
586    * 
587    * @see #setMaxFileSize(long)
588    */
589   public long getMaxFileSize() {
590     byte [] value = getValue(MAX_FILESIZE_KEY);
591     if (value != null)
592       return Long.valueOf(Bytes.toString(value)).longValue();
593     return HConstants.DEFAULT_MAX_FILE_SIZE;
594   }
595   
596   /**
597    * Sets the maximum size upto which a region can grow to after which a region
598    * split is triggered. The region size is represented by the size of the biggest 
599    * store file in that region, i.e. If the biggest store file grows beyond the 
600    * maxFileSize, then the region split is triggered. This defaults to a value of 
601    * 256 MB.
602    * <p>
603    * This is not an absolute value and might vary. Assume that a single row exceeds 
604    * the maxFileSize then the storeFileSize will be greater than maxFileSize since
605    * a single row cannot be split across multiple regions 
606    * </p>
607    * 
608    * @param maxFileSize The maximum file size that a store file can grow to
609    * before a split is triggered.
610    */
611   public void setMaxFileSize(long maxFileSize) {
612     setValue(MAX_FILESIZE_KEY, Bytes.toBytes(Long.toString(maxFileSize)));
613   }
614 
615   /**
616    * Returns the size of the memstore after which a flush to filesystem is triggered.
617    * 
618    * @return memory cache flush size for each hregion
619    * 
620    * @see #setMemStoreFlushSize(long)
621    */
622   public long getMemStoreFlushSize() {
623     byte [] value = getValue(MEMSTORE_FLUSHSIZE_KEY);
624     if (value != null)
625       return Long.valueOf(Bytes.toString(value)).longValue();
626     return DEFAULT_MEMSTORE_FLUSH_SIZE;
627   }
628 
629   /**
630    * Represents the maximum size of the memstore after which the contents of the 
631    * memstore are flushed to the filesystem. This defaults to a size of 64 MB.
632    * 
633    * @param memstoreFlushSize memory cache flush size for each hregion
634    */
635   public void setMemStoreFlushSize(long memstoreFlushSize) {
636     setValue(MEMSTORE_FLUSHSIZE_KEY,
637       Bytes.toBytes(Long.toString(memstoreFlushSize)));
638   }
639 
640   /**
641    * Adds a column family.
642    * @param family HColumnDescriptor of family to add.
643    */
644   public void addFamily(final HColumnDescriptor family) {
645     if (family.getName() == null || family.getName().length <= 0) {
646       throw new NullPointerException("Family name cannot be null or empty");
647     }
648     this.families.put(family.getName(), family);
649   }
650 
651   /**
652    * Checks to see if this table contains the given column family
653    * @param familyName Family name or column name.
654    * @return true if the table contains the specified family name
655    */
656   public boolean hasFamily(final byte [] familyName) {
657     return families.containsKey(familyName);
658   }
659 
660   /**
661    * @return Name of this table and then a map of all of the column family
662    * descriptors.
663    * @see #getNameAsString()
664    */
665   @Override
666   public String toString() {
667     StringBuilder s = new StringBuilder();
668     s.append('{');
669     s.append(HConstants.NAME);
670     s.append(" => '");
671     s.append(Bytes.toString(name));
672     s.append("'");
673     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
674         values.entrySet()) {
675       String key = Bytes.toString(e.getKey().get());
676       String value = Bytes.toString(e.getValue().get());
677       if (key == null) {
678         continue;
679       }
680       String upperCase = key.toUpperCase();
681       if (upperCase.equals(IS_ROOT) || upperCase.equals(IS_META)) {
682         // Skip. Don't bother printing out read-only values if false.
683         if (value.toLowerCase().equals(Boolean.FALSE.toString())) {
684           continue;
685         }
686       }
687       s.append(", ");
688       s.append(Bytes.toString(e.getKey().get()));
689       s.append(" => '");
690       s.append(Bytes.toString(e.getValue().get()));
691       s.append("'");
692     }
693     s.append(", ");
694     s.append(FAMILIES);
695     s.append(" => ");
696     s.append(families.values());
697     s.append('}');
698     return s.toString();
699   }
700 
701   /**
702    * @return Name of this table and then a map of all of the column family
703    * descriptors (with only the non-default column family attributes)
704    */
705   public String toStringCustomizedValues() {
706     StringBuilder s = new StringBuilder();
707     s.append('{');
708     s.append(HConstants.NAME);
709     s.append(" => '");
710     s.append(Bytes.toString(name));
711     s.append("'");
712     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
713         values.entrySet()) {
714       String key = Bytes.toString(e.getKey().get());
715       String value = Bytes.toString(e.getValue().get());
716       if (key == null) {
717         continue;
718       }
719       String upperCase = key.toUpperCase();
720       if (upperCase.equals(IS_ROOT) || upperCase.equals(IS_META)) {
721         // Skip. Don't bother printing out read-only values if false.
722         if (value.toLowerCase().equals(Boolean.FALSE.toString())) {
723           continue;
724         }
725       }
726       s.append(", ");
727       s.append(Bytes.toString(e.getKey().get()));
728       s.append(" => '");
729       s.append(Bytes.toString(e.getValue().get()));
730       s.append("'");
731     }
732     s.append(", ");
733     s.append(FAMILIES);
734     s.append(" => [");
735     int size = families.values().size();
736     int i = 0;
737     for(HColumnDescriptor hcd : families.values()) {
738       s.append(hcd.toStringCustomizedValues());
739       i++;
740       if( i != size)
741         s.append(", ");
742     }
743     s.append("]}");
744     return s.toString();
745   }
746 
747   /**
748    * Compare the contents of the descriptor with another one passed as a parameter. 
749    * Checks if the obj passed is an instance of HTableDescriptor, if yes then the
750    * contents of the descriptors are compared.
751    * 
752    * @return true if the contents of the the two descriptors exactly match
753    * 
754    * @see java.lang.Object#equals(java.lang.Object)
755    */
756   @Override
757   public boolean equals(Object obj) {
758     if (this == obj) {
759       return true;
760     }
761     if (obj == null) {
762       return false;
763     }
764     if (!(obj instanceof HTableDescriptor)) {
765       return false;
766     }
767     return compareTo((HTableDescriptor)obj) == 0;
768   }
769 
770   /**
771    * @see java.lang.Object#hashCode()
772    */
773   @Override
774   public int hashCode() {
775     int result = Bytes.hashCode(this.name);
776     result ^= Byte.valueOf(TABLE_DESCRIPTOR_VERSION).hashCode();
777     if (this.families != null && this.families.size() > 0) {
778       for (HColumnDescriptor e: this.families.values()) {
779         result ^= e.hashCode();
780       }
781     }
782     result ^= values.hashCode();
783     return result;
784   }
785 
786   // Writable
787   /**
788    * <em> INTERNAL </em> This method is a part of {@link WritableComparable} interface 
789    * and is used for de-serialization of the HTableDescriptor over RPC
790    */
791   public void readFields(DataInput in) throws IOException {
792     int version = in.readInt();
793     if (version < 3)
794       throw new IOException("versions < 3 are not supported (and never existed!?)");
795     // version 3+
796     name = Bytes.readByteArray(in);
797     nameAsString = Bytes.toString(this.name);
798     setRootRegion(in.readBoolean());
799     setMetaRegion(in.readBoolean());
800     values.clear();
801     int numVals = in.readInt();
802     for (int i = 0; i < numVals; i++) {
803       ImmutableBytesWritable key = new ImmutableBytesWritable();
804       ImmutableBytesWritable value = new ImmutableBytesWritable();
805       key.readFields(in);
806       value.readFields(in);
807       values.put(key, value);
808     }
809     families.clear();
810     int numFamilies = in.readInt();
811     for (int i = 0; i < numFamilies; i++) {
812       HColumnDescriptor c = new HColumnDescriptor();
813       c.readFields(in);
814       families.put(c.getName(), c);
815     }
816     if (version < 4) {
817       return;
818     }
819   }
820 
821   /**
822    * <em> INTERNAL </em> This method is a part of {@link WritableComparable} interface 
823    * and is used for serialization of the HTableDescriptor over RPC
824    */
825   public void write(DataOutput out) throws IOException {
826 	out.writeInt(TABLE_DESCRIPTOR_VERSION);
827     Bytes.writeByteArray(out, name);
828     out.writeBoolean(isRootRegion());
829     out.writeBoolean(isMetaRegion());
830     out.writeInt(values.size());
831     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
832         values.entrySet()) {
833       e.getKey().write(out);
834       e.getValue().write(out);
835     }
836     out.writeInt(families.size());
837     for(Iterator<HColumnDescriptor> it = families.values().iterator();
838         it.hasNext(); ) {
839       HColumnDescriptor family = it.next();
840       family.write(out);
841     }
842   }
843 
844   // Comparable
845 
846   /**
847    * Compares the descriptor with another descriptor which is passed as a parameter.
848    * This compares the content of the two descriptors and not the reference.
849    * 
850    * @return 0 if the contents of the descriptors are exactly matching, 
851    * 		 1 if there is a mismatch in the contents 
852    */
853   public int compareTo(final HTableDescriptor other) {
854     int result = Bytes.compareTo(this.name, other.name);
855     if (result == 0) {
856       result = families.size() - other.families.size();
857     }
858     if (result == 0 && families.size() != other.families.size()) {
859       result = Integer.valueOf(families.size()).compareTo(
860           Integer.valueOf(other.families.size()));
861     }
862     if (result == 0) {
863       for (Iterator<HColumnDescriptor> it = families.values().iterator(),
864           it2 = other.families.values().iterator(); it.hasNext(); ) {
865         result = it.next().compareTo(it2.next());
866         if (result != 0) {
867           break;
868         }
869       }
870     }
871     if (result == 0) {
872       // punt on comparison for ordering, just calculate difference
873       result = this.values.hashCode() - other.values.hashCode();
874       if (result < 0)
875         result = -1;
876       else if (result > 0)
877         result = 1;
878     }
879     return result;
880   }
881 
882   /**
883    * Returns an unmodifiable collection of all the {@link HColumnDescriptor} 
884    * of all the column families of the table.
885    *  
886    * @return Immutable collection of {@link HColumnDescriptor} of all the
887    * column families. 
888    */
889   public Collection<HColumnDescriptor> getFamilies() {
890     return Collections.unmodifiableCollection(this.families.values());
891   }
892 
893   /**
894    * Returns all the column family names of the current table. The map of 
895    * HTableDescriptor contains mapping of family name to HColumnDescriptors. 
896    * This returns all the keys of the family map which represents the column 
897    * family names of the table. 
898    * 
899    * @return Immutable sorted set of the keys of the families.
900    */
901   public Set<byte[]> getFamiliesKeys() {
902     return Collections.unmodifiableSet(this.families.keySet());
903   }
904 
905   /** 
906    * Returns an array all the {@link HColumnDescriptor} of the column families 
907    * of the table.
908    *  
909    * @return Array of all the HColumnDescriptors of the current table 
910    * 
911    * @see #getFamilies()
912    */
913   public HColumnDescriptor[] getColumnFamilies() {
914     return getFamilies().toArray(new HColumnDescriptor[0]);
915   }
916   
917 
918   /**
919    * Returns the HColumnDescriptor for a specific column family with name as 
920    * specified by the parameter column.
921    * 
922    * @param column Column family name 
923    * @return Column descriptor for the passed family name or the family on
924    * passed in column.
925    */
926   public HColumnDescriptor getFamily(final byte [] column) {
927     return this.families.get(column);
928   }
929   
930 
931   /**
932    * Removes the HColumnDescriptor with name specified by the parameter column 
933    * from the table descriptor
934    * 
935    * @param column Name of the column family to be removed.
936    * @return Column descriptor for the passed family name or the family on
937    * passed in column.
938    */
939   public HColumnDescriptor removeFamily(final byte [] column) {
940     return this.families.remove(column);
941   }
942   
943 
944   /**
945    * Add a table coprocessor to this table. The coprocessor
946    * type must be {@link org.apache.hadoop.hbase.coprocessor.RegionObserver}
947    * or Endpoint.
948    * It won't check if the class can be loaded or not.
949    * Whether a coprocessor is loadable or not will be determined when
950    * a region is opened.
951    * @param className Full class name.
952    * @throws IOException
953    */
954   public void addCoprocessor(String className) throws IOException {
955     addCoprocessor(className, null, Coprocessor.PRIORITY_USER, null);
956   }
957 
958   
959   /**
960    * Add a table coprocessor to this table. The coprocessor
961    * type must be {@link org.apache.hadoop.hbase.coprocessor.RegionObserver}
962    * or Endpoint.
963    * It won't check if the class can be loaded or not.
964    * Whether a coprocessor is loadable or not will be determined when
965    * a region is opened.
966    * @param jarFilePath Path of the jar file. If it's null, the class will be
967    * loaded from default classloader.
968    * @param className Full class name.
969    * @param priority Priority
970    * @param kvs Arbitrary key-value parameter pairs passed into the coprocessor.
971    * @throws IOException
972    */
973   public void addCoprocessor(String className, Path jarFilePath,
974                              int priority, final Map<String, String> kvs)
975   throws IOException {
976     if (hasCoprocessor(className)) {
977       throw new IOException("Coprocessor " + className + " already exists.");
978     }
979     // validate parameter kvs
980     StringBuilder kvString = new StringBuilder();
981     if (kvs != null) {
982       for (Map.Entry<String, String> e: kvs.entrySet()) {
983         if (!e.getKey().matches(HConstants.CP_HTD_ATTR_VALUE_PARAM_KEY_PATTERN)) {
984           throw new IOException("Illegal parameter key = " + e.getKey());
985         }
986         if (!e.getValue().matches(HConstants.CP_HTD_ATTR_VALUE_PARAM_VALUE_PATTERN)) {
987           throw new IOException("Illegal parameter (" + e.getKey() +
988               ") value = " + e.getValue());
989         }
990         if (kvString.length() != 0) {
991           kvString.append(',');
992         }
993         kvString.append(e.getKey());
994         kvString.append('=');
995         kvString.append(e.getValue());
996       }
997     }
998 
999     // generate a coprocessor key
1000     int maxCoprocessorNumber = 0;
1001     Matcher keyMatcher;
1002     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
1003         this.values.entrySet()) {
1004       keyMatcher =
1005           HConstants.CP_HTD_ATTR_KEY_PATTERN.matcher(
1006               Bytes.toString(e.getKey().get()));
1007       if (!keyMatcher.matches()) {
1008         continue;
1009       }
1010       maxCoprocessorNumber = Math.max(Integer.parseInt(keyMatcher.group(1)),
1011           maxCoprocessorNumber);
1012     }
1013     maxCoprocessorNumber++;
1014 
1015     String key = "coprocessor$" + Integer.toString(maxCoprocessorNumber);
1016     String value = ((jarFilePath == null)? "" : jarFilePath.toString()) +
1017         "|" + className + "|" + Integer.toString(priority) + "|" +
1018         kvString.toString();
1019     setValue(key, value);
1020   }
1021 
1022   
1023   /**
1024    * Check if the table has an attached co-processor represented by the name className
1025    * 
1026    * @param className - Class name of the co-processor
1027    * @return true of the table has a co-processor className
1028    */
1029   public boolean hasCoprocessor(String className) {
1030     Matcher keyMatcher;
1031     Matcher valueMatcher;
1032     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
1033         this.values.entrySet()) {
1034       keyMatcher =
1035           HConstants.CP_HTD_ATTR_KEY_PATTERN.matcher(
1036               Bytes.toString(e.getKey().get()));
1037       if (!keyMatcher.matches()) {
1038         continue;
1039       }
1040       valueMatcher =
1041         HConstants.CP_HTD_ATTR_VALUE_PATTERN.matcher(
1042             Bytes.toString(e.getValue().get()));
1043       if (!valueMatcher.matches()) {
1044         continue;
1045       }
1046       // get className and compare
1047       String clazz = valueMatcher.group(2).trim(); // classname is the 2nd field
1048       if (clazz.equals(className.trim())) {
1049         return true;
1050       }
1051     }
1052     return false;
1053   }
1054 
1055   
1056   /**
1057    * Returns the {@link Path} object representing the table directory under 
1058    * path rootdir 
1059    * 
1060    * @param rootdir qualified path of HBase root directory
1061    * @param tableName name of table
1062    * @return {@link Path} for table
1063    */
1064   public static Path getTableDir(Path rootdir, final byte [] tableName) {
1065     return new Path(rootdir, Bytes.toString(tableName));
1066   }
1067 
1068   /** Table descriptor for <core>-ROOT-</code> catalog table */
1069   public static final HTableDescriptor ROOT_TABLEDESC = new HTableDescriptor(
1070       HConstants.ROOT_TABLE_NAME,
1071       new HColumnDescriptor[] { new HColumnDescriptor(HConstants.CATALOG_FAMILY,
1072           10,  // Ten is arbitrary number.  Keep versions to help debugging.
1073           Compression.Algorithm.NONE.getName(), true, true, 8 * 1024,
1074           HConstants.FOREVER, StoreFile.BloomType.NONE.toString(),  
1075           HConstants.REPLICATION_SCOPE_LOCAL) });
1076 
1077   /** Table descriptor for <code>.META.</code> catalog table */
1078   public static final HTableDescriptor META_TABLEDESC = new HTableDescriptor(
1079       HConstants.META_TABLE_NAME, new HColumnDescriptor[] {
1080           new HColumnDescriptor(HConstants.CATALOG_FAMILY,
1081             10, // Ten is arbitrary number.  Keep versions to help debugging.
1082             Compression.Algorithm.NONE.getName(), true, true, 8 * 1024,
1083             HConstants.FOREVER, StoreFile.BloomType.NONE.toString(),
1084             HConstants.REPLICATION_SCOPE_LOCAL)});
1085 
1086 
1087   public void setOwner(User owner) {
1088     setOwnerString(owner != null ? owner.getShortName() : null);
1089   }
1090 
1091   // used by admin.rb:alter(table_name,*args) to update owner.
1092   public void setOwnerString(String ownerString) {
1093     if (ownerString != null) {
1094       setValue(OWNER_KEY, Bytes.toBytes(ownerString));
1095     } else {
1096       values.remove(OWNER_KEY);
1097     }
1098   }
1099 
1100   public String getOwnerString() {
1101     if (getValue(OWNER_KEY) != null) {
1102       return Bytes.toString(getValue(OWNER_KEY));
1103     }
1104     // Note that every table should have an owner (i.e. should have OWNER_KEY set).
1105     // .META. and -ROOT- should return system user as owner, not null (see MasterFileSystem.java:bootstrap()).
1106     return null;
1107   }
1108 }