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.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.hbase.classification.InterfaceStability;
32  import org.apache.hadoop.hbase.exceptions.DeserializationException;
33  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
34  import org.apache.hadoop.hbase.io.compress.Compression;
35  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
36  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
37  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.BytesBytesPair;
38  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.ColumnFamilySchema;
39  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.NameStringPair;
40  import org.apache.hadoop.hbase.regionserver.BloomType;
41  import org.apache.hadoop.hbase.util.Bytes;
42  import org.apache.hadoop.hbase.util.PrettyPrinter;
43  import org.apache.hadoop.hbase.util.PrettyPrinter.Unit;
44  import org.apache.hadoop.io.Text;
45  import org.apache.hadoop.io.WritableComparable;
46  
47  import com.google.common.base.Preconditions;
48  import org.apache.hadoop.hbase.util.ByteStringer;
49  
50  /**
51   * An HColumnDescriptor contains information about a column family such as the
52   * number of versions, compression settings, etc.
53   *
54   * It is used as input when creating a table or adding a column.
55   */
56  @InterfaceAudience.Public
57  @InterfaceStability.Evolving
58  public class HColumnDescriptor implements WritableComparable<HColumnDescriptor> {
59    // For future backward compatibility
60  
61    // Version  3 was when column names become byte arrays and when we picked up
62    // Time-to-live feature.  Version 4 was when we moved to byte arrays, HBASE-82.
63    // Version  5 was when bloom filter descriptors were removed.
64    // Version  6 adds metadata as a map where keys and values are byte[].
65    // Version  7 -- add new compression and hfile blocksize to HColumnDescriptor (HBASE-1217)
66    // Version  8 -- reintroduction of bloom filters, changed from boolean to enum
67    // Version  9 -- add data block encoding
68    // Version 10 -- change metadata to standard type.
69    // Version 11 -- add column family level configuration.
70    private static final byte COLUMN_DESCRIPTOR_VERSION = (byte) 11;
71  
72    // These constants are used as FileInfo keys
73    public static final String COMPRESSION = "COMPRESSION";
74    public static final String COMPRESSION_COMPACT = "COMPRESSION_COMPACT";
75    public static final String ENCODE_ON_DISK = // To be removed, it is not used anymore
76        "ENCODE_ON_DISK";
77    public static final String DATA_BLOCK_ENCODING =
78        "DATA_BLOCK_ENCODING";
79    public static final String BLOCKCACHE = "BLOCKCACHE";
80    public static final String CACHE_DATA_ON_WRITE = "CACHE_DATA_ON_WRITE";
81    public static final String CACHE_INDEX_ON_WRITE = "CACHE_INDEX_ON_WRITE";
82    public static final String CACHE_BLOOMS_ON_WRITE = "CACHE_BLOOMS_ON_WRITE";
83    public static final String EVICT_BLOCKS_ON_CLOSE = "EVICT_BLOCKS_ON_CLOSE";
84    /**
85     * Key for the PREFETCH_BLOCKS_ON_OPEN attribute.
86     * If set, all INDEX, BLOOM, and DATA blocks of HFiles belonging to this
87     * family will be loaded into the cache as soon as the file is opened. These
88     * loads will not count as cache misses.
89     */
90    public static final String PREFETCH_BLOCKS_ON_OPEN = "PREFETCH_BLOCKS_ON_OPEN";
91  
92    /**
93     * Size of storefile/hfile 'blocks'.  Default is {@link #DEFAULT_BLOCKSIZE}.
94     * Use smaller block sizes for faster random-access at expense of larger
95     * indices (more memory consumption).
96     */
97    public static final String BLOCKSIZE = "BLOCKSIZE";
98  
99    public static final String LENGTH = "LENGTH";
100   public static final String TTL = "TTL";
101   public static final String BLOOMFILTER = "BLOOMFILTER";
102   public static final String FOREVER = "FOREVER";
103   public static final String REPLICATION_SCOPE = "REPLICATION_SCOPE";
104   public static final byte[] REPLICATION_SCOPE_BYTES = Bytes.toBytes(REPLICATION_SCOPE);
105   public static final String MIN_VERSIONS = "MIN_VERSIONS";
106   public static final String KEEP_DELETED_CELLS = "KEEP_DELETED_CELLS";
107   public static final String COMPRESS_TAGS = "COMPRESS_TAGS";
108 
109   @InterfaceStability.Unstable
110   public static final String ENCRYPTION = "ENCRYPTION";
111   @InterfaceStability.Unstable
112   public static final String ENCRYPTION_KEY = "ENCRYPTION_KEY";
113 
114   public static final String DFS_REPLICATION = "DFS_REPLICATION";
115   public static final short DEFAULT_DFS_REPLICATION = 0;
116 
117   /**
118    * Default compression type.
119    */
120   public static final String DEFAULT_COMPRESSION =
121     Compression.Algorithm.NONE.getName();
122 
123   /**
124    * Default value of the flag that enables data block encoding on disk, as
125    * opposed to encoding in cache only. We encode blocks everywhere by default,
126    * as long as {@link #DATA_BLOCK_ENCODING} is not NONE.
127    */
128   public static final boolean DEFAULT_ENCODE_ON_DISK = true;
129 
130   /** Default data block encoding algorithm. */
131   public static final String DEFAULT_DATA_BLOCK_ENCODING =
132       DataBlockEncoding.NONE.toString();
133 
134   /**
135    * Default number of versions of a record to keep.
136    */
137   public static final int DEFAULT_VERSIONS = HBaseConfiguration.create().getInt(
138     "hbase.column.max.version", 1);
139 
140   /**
141    * Default is not to keep a minimum of versions.
142    */
143   public static final int DEFAULT_MIN_VERSIONS = 0;
144 
145   /*
146    * Cache here the HCD value.
147    * Question: its OK to cache since when we're reenable, we create a new HCD?
148    */
149   private volatile Integer blocksize = null;
150 
151   /**
152    * Default setting for whether to serve from memory or not.
153    */
154   public static final boolean DEFAULT_IN_MEMORY = false;
155 
156   /**
157    * Default setting for preventing deleted from being collected immediately.
158    */
159   public static final KeepDeletedCells DEFAULT_KEEP_DELETED = KeepDeletedCells.FALSE;
160 
161   /**
162    * Default setting for whether to use a block cache or not.
163    */
164   public static final boolean DEFAULT_BLOCKCACHE = true;
165 
166   /**
167    * Default setting for whether to cache data blocks on write if block caching
168    * is enabled.
169    */
170   public static final boolean DEFAULT_CACHE_DATA_ON_WRITE = false;
171 
172   /**
173    * Default setting for whether to cache index blocks on write if block
174    * caching is enabled.
175    */
176   public static final boolean DEFAULT_CACHE_INDEX_ON_WRITE = false;
177 
178   /**
179    * Default size of blocks in files stored to the filesytem (hfiles).
180    */
181   public static final int DEFAULT_BLOCKSIZE = HConstants.DEFAULT_BLOCKSIZE;
182 
183   /**
184    * Default setting for whether or not to use bloomfilters.
185    */
186   public static final String DEFAULT_BLOOMFILTER = BloomType.ROW.toString();
187 
188   /**
189    * Default setting for whether to cache bloom filter blocks on write if block
190    * caching is enabled.
191    */
192   public static final boolean DEFAULT_CACHE_BLOOMS_ON_WRITE = false;
193 
194   /**
195    * Default time to live of cell contents.
196    */
197   public static final int DEFAULT_TTL = HConstants.FOREVER;
198 
199   /**
200    * Default scope.
201    */
202   public static final int DEFAULT_REPLICATION_SCOPE = HConstants.REPLICATION_SCOPE_LOCAL;
203 
204   /**
205    * Default setting for whether to evict cached blocks from the blockcache on
206    * close.
207    */
208   public static final boolean DEFAULT_EVICT_BLOCKS_ON_CLOSE = false;
209 
210   /**
211    * Default compress tags along with any type of DataBlockEncoding.
212    */
213   public static final boolean DEFAULT_COMPRESS_TAGS = true;
214 
215   /*
216    * Default setting for whether to prefetch blocks into the blockcache on open.
217    */
218   public static final boolean DEFAULT_PREFETCH_BLOCKS_ON_OPEN = false;
219 
220   private final static Map<String, String> DEFAULT_VALUES
221     = new HashMap<String, String>();
222   private final static Set<ImmutableBytesWritable> RESERVED_KEYWORDS
223     = new HashSet<ImmutableBytesWritable>();
224   static {
225       DEFAULT_VALUES.put(BLOOMFILTER, DEFAULT_BLOOMFILTER);
226       DEFAULT_VALUES.put(REPLICATION_SCOPE, String.valueOf(DEFAULT_REPLICATION_SCOPE));
227       DEFAULT_VALUES.put(HConstants.VERSIONS, String.valueOf(DEFAULT_VERSIONS));
228       DEFAULT_VALUES.put(MIN_VERSIONS, String.valueOf(DEFAULT_MIN_VERSIONS));
229       DEFAULT_VALUES.put(COMPRESSION, DEFAULT_COMPRESSION);
230       DEFAULT_VALUES.put(TTL, String.valueOf(DEFAULT_TTL));
231       DEFAULT_VALUES.put(BLOCKSIZE, String.valueOf(DEFAULT_BLOCKSIZE));
232       DEFAULT_VALUES.put(HConstants.IN_MEMORY, String.valueOf(DEFAULT_IN_MEMORY));
233       DEFAULT_VALUES.put(BLOCKCACHE, String.valueOf(DEFAULT_BLOCKCACHE));
234       DEFAULT_VALUES.put(KEEP_DELETED_CELLS, String.valueOf(DEFAULT_KEEP_DELETED));
235       DEFAULT_VALUES.put(DATA_BLOCK_ENCODING, String.valueOf(DEFAULT_DATA_BLOCK_ENCODING));
236       DEFAULT_VALUES.put(CACHE_DATA_ON_WRITE, String.valueOf(DEFAULT_CACHE_DATA_ON_WRITE));
237       DEFAULT_VALUES.put(CACHE_INDEX_ON_WRITE, String.valueOf(DEFAULT_CACHE_INDEX_ON_WRITE));
238       DEFAULT_VALUES.put(CACHE_BLOOMS_ON_WRITE, String.valueOf(DEFAULT_CACHE_BLOOMS_ON_WRITE));
239       DEFAULT_VALUES.put(EVICT_BLOCKS_ON_CLOSE, String.valueOf(DEFAULT_EVICT_BLOCKS_ON_CLOSE));
240       DEFAULT_VALUES.put(PREFETCH_BLOCKS_ON_OPEN, String.valueOf(DEFAULT_PREFETCH_BLOCKS_ON_OPEN));
241       for (String s : DEFAULT_VALUES.keySet()) {
242         RESERVED_KEYWORDS.add(new ImmutableBytesWritable(Bytes.toBytes(s)));
243       }
244       RESERVED_KEYWORDS.add(new ImmutableBytesWritable(Bytes.toBytes(ENCRYPTION)));
245       RESERVED_KEYWORDS.add(new ImmutableBytesWritable(Bytes.toBytes(ENCRYPTION_KEY)));
246   }
247 
248   private static final int UNINITIALIZED = -1;
249 
250   // Column family name
251   private byte [] name;
252 
253   // Column metadata
254   private final Map<ImmutableBytesWritable, ImmutableBytesWritable> values =
255     new HashMap<ImmutableBytesWritable,ImmutableBytesWritable>();
256 
257   /**
258    * A map which holds the configuration specific to the column family.
259    * The keys of the map have the same names as config keys and override the defaults with
260    * cf-specific settings. Example usage may be for compactions, etc.
261    */
262   private final Map<String, String> configuration = new HashMap<String, String>();
263 
264   /*
265    * Cache the max versions rather than calculate it every time.
266    */
267   private int cachedMaxVersions = UNINITIALIZED;
268 
269   /**
270    * Default constructor. Must be present for Writable.
271    * @deprecated Used by Writables and Writables are going away.
272    */
273   @Deprecated
274   // Make this private rather than remove after deprecation period elapses.  Its needed by pb
275   // deserializations.
276   public HColumnDescriptor() {
277     this.name = null;
278   }
279 
280   /**
281    * Construct a column descriptor specifying only the family name
282    * The other attributes are defaulted.
283    *
284    * @param familyName Column family name. Must be 'printable' -- digit or
285    * letter -- and may not contain a <code>:<code>
286    */
287   public HColumnDescriptor(final String familyName) {
288     this(Bytes.toBytes(familyName));
289   }
290 
291   /**
292    * Construct a column descriptor specifying only the family name
293    * The other attributes are defaulted.
294    *
295    * @param familyName Column family name. Must be 'printable' -- digit or
296    * letter -- and may not contain a <code>:<code>
297    */
298   public HColumnDescriptor(final byte [] familyName) {
299     this (familyName == null || familyName.length <= 0?
300       HConstants.EMPTY_BYTE_ARRAY: familyName, DEFAULT_VERSIONS,
301       DEFAULT_COMPRESSION, DEFAULT_IN_MEMORY, DEFAULT_BLOCKCACHE,
302       DEFAULT_TTL, DEFAULT_BLOOMFILTER);
303   }
304 
305   /**
306    * Constructor.
307    * Makes a deep copy of the supplied descriptor.
308    * Can make a modifiable descriptor from an UnmodifyableHColumnDescriptor.
309    * @param desc The descriptor.
310    */
311   public HColumnDescriptor(HColumnDescriptor desc) {
312     super();
313     this.name = desc.name.clone();
314     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
315         desc.values.entrySet()) {
316       this.values.put(e.getKey(), e.getValue());
317     }
318     for (Map.Entry<String, String> e : desc.configuration.entrySet()) {
319       this.configuration.put(e.getKey(), e.getValue());
320     }
321     setMaxVersions(desc.getMaxVersions());
322   }
323 
324   /**
325    * Constructor
326    * @param familyName Column family name. Must be 'printable' -- digit or
327    * letter -- and may not contain a <code>:<code>
328    * @param maxVersions Maximum number of versions to keep
329    * @param compression Compression type
330    * @param inMemory If true, column data should be kept in an HRegionServer's
331    * cache
332    * @param blockCacheEnabled If true, MapFile blocks should be cached
333    * @param timeToLive Time-to-live of cell contents, in seconds
334    * (use HConstants.FOREVER for unlimited TTL)
335    * @param bloomFilter Bloom filter type for this column
336    *
337    * @throws IllegalArgumentException if passed a family name that is made of
338    * other than 'word' characters: i.e. <code>[a-zA-Z_0-9]</code> or contains
339    * a <code>:</code>
340    * @throws IllegalArgumentException if the number of versions is &lt;= 0
341    * @deprecated use {@link #HColumnDescriptor(String)} and setters
342    */
343   @Deprecated
344   public HColumnDescriptor(final byte [] familyName, final int maxVersions,
345       final String compression, final boolean inMemory,
346       final boolean blockCacheEnabled,
347       final int timeToLive, final String bloomFilter) {
348     this(familyName, maxVersions, compression, inMemory, blockCacheEnabled,
349       DEFAULT_BLOCKSIZE, timeToLive, bloomFilter, DEFAULT_REPLICATION_SCOPE);
350   }
351 
352   /**
353    * Constructor
354    * @param familyName Column family name. Must be 'printable' -- digit or
355    * letter -- and may not contain a <code>:<code>
356    * @param maxVersions Maximum number of versions to keep
357    * @param compression Compression type
358    * @param inMemory If true, column data should be kept in an HRegionServer's
359    * cache
360    * @param blockCacheEnabled If true, MapFile blocks should be cached
361    * @param blocksize Block size to use when writing out storefiles.  Use
362    * smaller block sizes for faster random-access at expense of larger indices
363    * (more memory consumption).  Default is usually 64k.
364    * @param timeToLive Time-to-live of cell contents, in seconds
365    * (use HConstants.FOREVER for unlimited TTL)
366    * @param bloomFilter Bloom filter type for this column
367    * @param scope The scope tag for this column
368    *
369    * @throws IllegalArgumentException if passed a family name that is made of
370    * other than 'word' characters: i.e. <code>[a-zA-Z_0-9]</code> or contains
371    * a <code>:</code>
372    * @throws IllegalArgumentException if the number of versions is &lt;= 0
373    * @deprecated use {@link #HColumnDescriptor(String)} and setters
374    */
375   @Deprecated
376   public HColumnDescriptor(final byte [] familyName, final int maxVersions,
377       final String compression, final boolean inMemory,
378       final boolean blockCacheEnabled, final int blocksize,
379       final int timeToLive, final String bloomFilter, final int scope) {
380     this(familyName, DEFAULT_MIN_VERSIONS, maxVersions, DEFAULT_KEEP_DELETED,
381         compression, DEFAULT_ENCODE_ON_DISK, DEFAULT_DATA_BLOCK_ENCODING,
382         inMemory, blockCacheEnabled, blocksize, timeToLive, bloomFilter,
383         scope);
384   }
385 
386   /**
387    * Constructor
388    * @param familyName Column family name. Must be 'printable' -- digit or
389    * letter -- and may not contain a <code>:<code>
390    * @param minVersions Minimum number of versions to keep
391    * @param maxVersions Maximum number of versions to keep
392    * @param keepDeletedCells Whether to retain deleted cells until they expire
393    *        up to maxVersions versions.
394    * @param compression Compression type
395    * @param encodeOnDisk whether to use the specified data block encoding
396    *        on disk. If false, the encoding will be used in cache only.
397    * @param dataBlockEncoding data block encoding
398    * @param inMemory If true, column data should be kept in an HRegionServer's
399    * cache
400    * @param blockCacheEnabled If true, MapFile blocks should be cached
401    * @param blocksize Block size to use when writing out storefiles.  Use
402    * smaller blocksizes for faster random-access at expense of larger indices
403    * (more memory consumption).  Default is usually 64k.
404    * @param timeToLive Time-to-live of cell contents, in seconds
405    * (use HConstants.FOREVER for unlimited TTL)
406    * @param bloomFilter Bloom filter type for this column
407    * @param scope The scope tag for this column
408    *
409    * @throws IllegalArgumentException if passed a family name that is made of
410    * other than 'word' characters: i.e. <code>[a-zA-Z_0-9]</code> or contains
411    * a <code>:</code>
412    * @throws IllegalArgumentException if the number of versions is &lt;= 0
413    * @deprecated use {@link #HColumnDescriptor(String)} and setters
414    */
415   @Deprecated
416   public HColumnDescriptor(final byte[] familyName, final int minVersions,
417       final int maxVersions, final KeepDeletedCells keepDeletedCells,
418       final String compression, final boolean encodeOnDisk,
419       final String dataBlockEncoding, final boolean inMemory,
420       final boolean blockCacheEnabled, final int blocksize,
421       final int timeToLive, final String bloomFilter, final int scope) {
422     isLegalFamilyName(familyName);
423     this.name = familyName;
424 
425     if (maxVersions <= 0) {
426       // TODO: Allow maxVersion of 0 to be the way you say "Keep all versions".
427       // Until there is support, consider 0 or < 0 -- a configuration error.
428       throw new IllegalArgumentException("Maximum versions must be positive");
429     }
430 
431     if (minVersions > 0) {
432       if (timeToLive == HConstants.FOREVER) {
433         throw new IllegalArgumentException("Minimum versions requires TTL.");
434       }
435       if (minVersions >= maxVersions) {
436         throw new IllegalArgumentException("Minimum versions must be < "
437             + "maximum versions.");
438       }
439     }
440 
441     setMaxVersions(maxVersions);
442     setMinVersions(minVersions);
443     setKeepDeletedCells(keepDeletedCells);
444     setInMemory(inMemory);
445     setBlockCacheEnabled(blockCacheEnabled);
446     setTimeToLive(timeToLive);
447     setCompressionType(Compression.Algorithm.
448       valueOf(compression.toUpperCase()));
449     setDataBlockEncoding(DataBlockEncoding.
450         valueOf(dataBlockEncoding.toUpperCase()));
451     setBloomFilterType(BloomType.
452       valueOf(bloomFilter.toUpperCase()));
453     setBlocksize(blocksize);
454     setScope(scope);
455   }
456 
457   /**
458    * @param b Family name.
459    * @return <code>b</code>
460    * @throws IllegalArgumentException If not null and not a legitimate family
461    * name: i.e. 'printable' and ends in a ':' (Null passes are allowed because
462    * <code>b</code> can be null when deserializing).  Cannot start with a '.'
463    * either. Also Family can not be an empty value or equal "recovered.edits".
464    */
465   public static byte [] isLegalFamilyName(final byte [] b) {
466     if (b == null) {
467       return b;
468     }
469     Preconditions.checkArgument(b.length != 0, "Family name can not be empty");
470     if (b[0] == '.') {
471       throw new IllegalArgumentException("Family names cannot start with a " +
472         "period: " + Bytes.toString(b));
473     }
474     for (int i = 0; i < b.length; i++) {
475       if (Character.isISOControl(b[i]) || b[i] == ':' || b[i] == '\\' || b[i] == '/') {
476         throw new IllegalArgumentException("Illegal character <" + b[i] +
477           ">. Family names cannot contain control characters or colons: " +
478           Bytes.toString(b));
479       }
480     }
481     byte[] recoveredEdit = Bytes.toBytes(HConstants.RECOVERED_EDITS_DIR);
482     if (Bytes.equals(recoveredEdit, b)) {
483       throw new IllegalArgumentException("Family name cannot be: " +
484           HConstants.RECOVERED_EDITS_DIR);
485     }
486     return b;
487   }
488 
489   /**
490    * @return Name of this column family
491    */
492   public byte [] getName() {
493     return name;
494   }
495 
496   /**
497    * @return Name of this column family
498    */
499   public String getNameAsString() {
500     return Bytes.toString(this.name);
501   }
502 
503   /**
504    * @param key The key.
505    * @return The value.
506    */
507   public byte[] getValue(byte[] key) {
508     ImmutableBytesWritable ibw = values.get(new ImmutableBytesWritable(key));
509     if (ibw == null)
510       return null;
511     return ibw.get();
512   }
513 
514   /**
515    * @param key The key.
516    * @return The value as a string.
517    */
518   public String getValue(String key) {
519     byte[] value = getValue(Bytes.toBytes(key));
520     if (value == null)
521       return null;
522     return Bytes.toString(value);
523   }
524 
525   /**
526    * @return All values.
527    */
528   public Map<ImmutableBytesWritable,ImmutableBytesWritable> getValues() {
529     // shallow pointer copy
530     return Collections.unmodifiableMap(values);
531   }
532 
533   /**
534    * @param key The key.
535    * @param value The value.
536    * @return this (for chained invocation)
537    */
538   public HColumnDescriptor setValue(byte[] key, byte[] value) {
539     values.put(new ImmutableBytesWritable(key),
540       new ImmutableBytesWritable(value));
541     return this;
542   }
543 
544   /**
545    * @param key Key whose key and value we're to remove from HCD parameters.
546    */
547   public void remove(final byte [] key) {
548     values.remove(new ImmutableBytesWritable(key));
549   }
550 
551   /**
552    * @param key The key.
553    * @param value The value.
554    * @return this (for chained invocation)
555    */
556   public HColumnDescriptor setValue(String key, String value) {
557     if (value == null) {
558       remove(Bytes.toBytes(key));
559     } else {
560       setValue(Bytes.toBytes(key), Bytes.toBytes(value));
561     }
562     return this;
563   }
564 
565   /** @return compression type being used for the column family */
566   public Compression.Algorithm getCompression() {
567     String n = getValue(COMPRESSION);
568     if (n == null) {
569       return Compression.Algorithm.NONE;
570     }
571     return Compression.Algorithm.valueOf(n.toUpperCase());
572   }
573 
574   /** @return compression type being used for the column family for major
575       compression */
576   public Compression.Algorithm getCompactionCompression() {
577     String n = getValue(COMPRESSION_COMPACT);
578     if (n == null) {
579       return getCompression();
580     }
581     return Compression.Algorithm.valueOf(n.toUpperCase());
582   }
583 
584   /** @return maximum number of versions */
585   public int getMaxVersions() {
586     if (this.cachedMaxVersions == UNINITIALIZED) {
587       String v = getValue(HConstants.VERSIONS);
588       this.cachedMaxVersions = Integer.parseInt(v);
589     }
590     return this.cachedMaxVersions;
591   }
592 
593   /**
594    * @param maxVersions maximum number of versions
595    * @return this (for chained invocation)
596    */
597   public HColumnDescriptor setMaxVersions(int maxVersions) {
598     if (maxVersions <= 0) {
599       // TODO: Allow maxVersion of 0 to be the way you say "Keep all versions".
600       // Until there is support, consider 0 or < 0 -- a configuration error.
601       throw new IllegalArgumentException("Maximum versions must be positive");
602     }    
603     if (maxVersions < this.getMinVersions()) {      
604         throw new IllegalArgumentException("Set MaxVersion to " + maxVersions
605             + " while minVersion is " + this.getMinVersions()
606             + ". Maximum versions must be >= minimum versions ");      
607     }
608     setValue(HConstants.VERSIONS, Integer.toString(maxVersions));
609     cachedMaxVersions = maxVersions;
610     return this;
611   }
612 
613   /**
614    * Set minimum and maximum versions to keep
615    *
616    * @param minVersions minimal number of versions
617    * @param maxVersions maximum number of versions
618    * @return this (for chained invocation)
619    */
620   public HColumnDescriptor setVersions(int minVersions, int maxVersions) {
621     if (minVersions <= 0) {
622       // TODO: Allow minVersion and maxVersion of 0 to be the way you say "Keep all versions".
623       // Until there is support, consider 0 or < 0 -- a configuration error.
624       throw new IllegalArgumentException("Minimum versions must be positive");
625     }
626 
627     if (maxVersions < minVersions) {
628       throw new IllegalArgumentException("Unable to set MaxVersion to " + maxVersions
629         + " and set MinVersion to " + minVersions
630         + ", as maximum versions must be >= minimum versions.");
631     }
632     setMinVersions(minVersions);
633     setMaxVersions(maxVersions);
634     return this;
635   }
636 
637   /**
638    * @return The storefile/hfile blocksize for this column family.
639    */
640   public synchronized int getBlocksize() {
641     if (this.blocksize == null) {
642       String value = getValue(BLOCKSIZE);
643       this.blocksize = (value != null)?
644         Integer.decode(value): Integer.valueOf(DEFAULT_BLOCKSIZE);
645     }
646     return this.blocksize.intValue();
647   }
648 
649   /**
650    * @param s Blocksize to use when writing out storefiles/hfiles on this
651    * column family.
652    * @return this (for chained invocation)
653    */
654   public HColumnDescriptor setBlocksize(int s) {
655     setValue(BLOCKSIZE, Integer.toString(s));
656     this.blocksize = null;
657     return this;
658   }
659 
660   /**
661    * @return Compression type setting.
662    */
663   public Compression.Algorithm getCompressionType() {
664     return getCompression();
665   }
666 
667   /**
668    * Compression types supported in hbase.
669    * LZO is not bundled as part of the hbase distribution.
670    * See <a href="http://wiki.apache.org/hadoop/UsingLzoCompression">LZO Compression</a>
671    * for how to enable it.
672    * @param type Compression type setting.
673    * @return this (for chained invocation)
674    */
675   public HColumnDescriptor setCompressionType(Compression.Algorithm type) {
676     return setValue(COMPRESSION, type.getName().toUpperCase());
677   }
678 
679   /** @return data block encoding algorithm used on disk */
680   @Deprecated
681   public DataBlockEncoding getDataBlockEncodingOnDisk() {
682     return getDataBlockEncoding();
683   }
684 
685   /**
686    * This method does nothing now. Flag ENCODE_ON_DISK is not used
687    * any more. Data blocks have the same encoding in cache as on disk.
688    * @return this (for chained invocation)
689    */
690   @Deprecated
691   public HColumnDescriptor setEncodeOnDisk(boolean encodeOnDisk) {
692     return this;
693   }
694 
695   /**
696    * @return the data block encoding algorithm used in block cache and
697    *         optionally on disk
698    */
699   public DataBlockEncoding getDataBlockEncoding() {
700     String type = getValue(DATA_BLOCK_ENCODING);
701     if (type == null) {
702       type = DEFAULT_DATA_BLOCK_ENCODING;
703     }
704     return DataBlockEncoding.valueOf(type);
705   }
706 
707   /**
708    * Set data block encoding algorithm used in block cache.
709    * @param type What kind of data block encoding will be used.
710    * @return this (for chained invocation)
711    */
712   public HColumnDescriptor setDataBlockEncoding(DataBlockEncoding type) {
713     String name;
714     if (type != null) {
715       name = type.toString();
716     } else {
717       name = DataBlockEncoding.NONE.toString();
718     }
719     return setValue(DATA_BLOCK_ENCODING, name);
720   }
721 
722   /**
723    * Set whether the tags should be compressed along with DataBlockEncoding. When no
724    * DataBlockEncoding is been used, this is having no effect.
725    * 
726    * @param compressTags
727    * @return this (for chained invocation)
728    */
729   public HColumnDescriptor setCompressTags(boolean compressTags) {
730     return setValue(COMPRESS_TAGS, String.valueOf(compressTags));
731   }
732 
733   /**
734    * @return Whether KV tags should be compressed along with DataBlockEncoding. When no
735    *         DataBlockEncoding is been used, this is having no effect.
736    */
737   public boolean shouldCompressTags() {
738     String compressTagsStr = getValue(COMPRESS_TAGS);
739     boolean compressTags = DEFAULT_COMPRESS_TAGS;
740     if (compressTagsStr != null) {
741       compressTags = Boolean.valueOf(compressTagsStr);
742     }
743     return compressTags;
744   }
745 
746   /**
747    * @return Compression type setting.
748    */
749   public Compression.Algorithm getCompactionCompressionType() {
750     return getCompactionCompression();
751   }
752 
753   /**
754    * Compression types supported in hbase.
755    * LZO is not bundled as part of the hbase distribution.
756    * See <a href="http://wiki.apache.org/hadoop/UsingLzoCompression">LZO Compression</a>
757    * for how to enable it.
758    * @param type Compression type setting.
759    * @return this (for chained invocation)
760    */
761   public HColumnDescriptor setCompactionCompressionType(
762       Compression.Algorithm type) {
763     return setValue(COMPRESSION_COMPACT, type.getName().toUpperCase());
764   }
765 
766   /**
767    * @return True if we are to keep all in use HRegionServer cache.
768    */
769   public boolean isInMemory() {
770     String value = getValue(HConstants.IN_MEMORY);
771     if (value != null)
772       return Boolean.valueOf(value).booleanValue();
773     return DEFAULT_IN_MEMORY;
774   }
775 
776   /**
777    * @param inMemory True if we are to keep all values in the HRegionServer
778    * cache
779    * @return this (for chained invocation)
780    */
781   public HColumnDescriptor setInMemory(boolean inMemory) {
782     return setValue(HConstants.IN_MEMORY, Boolean.toString(inMemory));
783   }
784 
785   public KeepDeletedCells getKeepDeletedCells() {
786     String value = getValue(KEEP_DELETED_CELLS);
787     if (value != null) {
788       // toUpperCase for backwards compatibility
789       return KeepDeletedCells.valueOf(value.toUpperCase());
790     }
791     return DEFAULT_KEEP_DELETED;
792   }
793 
794   /**
795    * @param keepDeletedCells True if deleted rows should not be collected
796    * immediately.
797    * @return this (for chained invocation)
798    * @deprecated use {@link #setKeepDeletedCells(KeepDeletedCells)}
799    */
800   @Deprecated
801   public HColumnDescriptor setKeepDeletedCells(boolean keepDeletedCells) {
802     return setValue(KEEP_DELETED_CELLS, (keepDeletedCells ? KeepDeletedCells.TRUE
803         : KeepDeletedCells.FALSE).toString());
804   }
805 
806   /**
807    * @param keepDeletedCells True if deleted rows should not be collected
808    * immediately.
809    * @return this (for chained invocation)
810    */
811   public HColumnDescriptor setKeepDeletedCells(KeepDeletedCells keepDeletedCells) {
812     return setValue(KEEP_DELETED_CELLS, keepDeletedCells.toString());
813   }
814 
815   /**
816    * @return Time-to-live of cell contents, in seconds.
817    */
818   public int getTimeToLive() {
819     String value = getValue(TTL);
820     return (value != null)? Integer.valueOf(value).intValue(): DEFAULT_TTL;
821   }
822 
823   /**
824    * @param timeToLive Time-to-live of cell contents, in seconds.
825    * @return this (for chained invocation)
826    */
827   public HColumnDescriptor setTimeToLive(int timeToLive) {
828     return setValue(TTL, Integer.toString(timeToLive));
829   }
830 
831   /**
832    * @return The minimum number of versions to keep.
833    */
834   public int getMinVersions() {
835     String value = getValue(MIN_VERSIONS);
836     return (value != null)? Integer.valueOf(value).intValue(): 0;
837   }
838 
839   /**
840    * @param minVersions The minimum number of versions to keep.
841    * (used when timeToLive is set)
842    * @return this (for chained invocation)
843    */
844   public HColumnDescriptor setMinVersions(int minVersions) {
845     return setValue(MIN_VERSIONS, Integer.toString(minVersions));
846   }
847 
848   /**
849    * @return True if MapFile blocks should be cached.
850    */
851   public boolean isBlockCacheEnabled() {
852     String value = getValue(BLOCKCACHE);
853     if (value != null)
854       return Boolean.valueOf(value).booleanValue();
855     return DEFAULT_BLOCKCACHE;
856   }
857 
858   /**
859    * @param blockCacheEnabled True if MapFile blocks should be cached.
860    * @return this (for chained invocation)
861    */
862   public HColumnDescriptor setBlockCacheEnabled(boolean blockCacheEnabled) {
863     return setValue(BLOCKCACHE, Boolean.toString(blockCacheEnabled));
864   }
865 
866   /**
867    * @return bloom filter type used for new StoreFiles in ColumnFamily
868    */
869   public BloomType getBloomFilterType() {
870     String n = getValue(BLOOMFILTER);
871     if (n == null) {
872       n = DEFAULT_BLOOMFILTER;
873     }
874     return BloomType.valueOf(n.toUpperCase());
875   }
876 
877   /**
878    * @param bt bloom filter type
879    * @return this (for chained invocation)
880    */
881   public HColumnDescriptor setBloomFilterType(final BloomType bt) {
882     return setValue(BLOOMFILTER, bt.toString());
883   }
884 
885    /**
886     * @return the scope tag
887     */
888   public int getScope() {
889     byte[] value = getValue(REPLICATION_SCOPE_BYTES);
890     if (value != null) {
891       return Integer.valueOf(Bytes.toString(value));
892     }
893     return DEFAULT_REPLICATION_SCOPE;
894   }
895 
896  /**
897   * @param scope the scope tag
898   * @return this (for chained invocation)
899   */
900   public HColumnDescriptor setScope(int scope) {
901     return setValue(REPLICATION_SCOPE, Integer.toString(scope));
902   }
903 
904   /**
905    * @return true if we should cache data blocks on write
906    */
907   public boolean shouldCacheDataOnWrite() {
908     String value = getValue(CACHE_DATA_ON_WRITE);
909     if (value != null) {
910       return Boolean.valueOf(value).booleanValue();
911     }
912     return DEFAULT_CACHE_DATA_ON_WRITE;
913   }
914 
915   /**
916    * @param value true if we should cache data blocks on write
917    * @return this (for chained invocation)
918    */
919   public HColumnDescriptor setCacheDataOnWrite(boolean value) {
920     return setValue(CACHE_DATA_ON_WRITE, Boolean.toString(value));
921   }
922 
923   /**
924    * @return true if we should cache index blocks on write
925    */
926   public boolean shouldCacheIndexesOnWrite() {
927     String value = getValue(CACHE_INDEX_ON_WRITE);
928     if (value != null) {
929       return Boolean.valueOf(value).booleanValue();
930     }
931     return DEFAULT_CACHE_INDEX_ON_WRITE;
932   }
933 
934   /**
935    * @param value true if we should cache index blocks on write
936    * @return this (for chained invocation)
937    */
938   public HColumnDescriptor setCacheIndexesOnWrite(boolean value) {
939     return setValue(CACHE_INDEX_ON_WRITE, Boolean.toString(value));
940   }
941 
942   /**
943    * @return true if we should cache bloomfilter blocks on write
944    */
945   public boolean shouldCacheBloomsOnWrite() {
946     String value = getValue(CACHE_BLOOMS_ON_WRITE);
947     if (value != null) {
948       return Boolean.valueOf(value).booleanValue();
949     }
950     return DEFAULT_CACHE_BLOOMS_ON_WRITE;
951   }
952 
953   /**
954    * @param value true if we should cache bloomfilter blocks on write
955    * @return this (for chained invocation)
956    */
957   public HColumnDescriptor setCacheBloomsOnWrite(boolean value) {
958     return setValue(CACHE_BLOOMS_ON_WRITE, Boolean.toString(value));
959   }
960 
961   /**
962    * @return true if we should evict cached blocks from the blockcache on
963    * close
964    */
965   public boolean shouldEvictBlocksOnClose() {
966     String value = getValue(EVICT_BLOCKS_ON_CLOSE);
967     if (value != null) {
968       return Boolean.valueOf(value).booleanValue();
969     }
970     return DEFAULT_EVICT_BLOCKS_ON_CLOSE;
971   }
972 
973   /**
974    * @param value true if we should evict cached blocks from the blockcache on
975    * close
976    * @return this (for chained invocation)
977    */
978   public HColumnDescriptor setEvictBlocksOnClose(boolean value) {
979     return setValue(EVICT_BLOCKS_ON_CLOSE, Boolean.toString(value));
980   }
981 
982   /**
983    * @return true if we should prefetch blocks into the blockcache on open
984    */
985   public boolean shouldPrefetchBlocksOnOpen() {
986     String value = getValue(PREFETCH_BLOCKS_ON_OPEN);
987    if (value != null) {
988       return Boolean.valueOf(value).booleanValue();
989     }
990     return DEFAULT_PREFETCH_BLOCKS_ON_OPEN;
991   }
992 
993   /**
994    * @param value true if we should prefetch blocks into the blockcache on open
995    * @return this (for chained invocation)
996    */
997   public HColumnDescriptor setPrefetchBlocksOnOpen(boolean value) {
998     return setValue(PREFETCH_BLOCKS_ON_OPEN, Boolean.toString(value));
999   }
1000 
1001   /**
1002    * @see java.lang.Object#toString()
1003    */
1004   @Override
1005   public String toString() {
1006     StringBuilder s = new StringBuilder();
1007 
1008     s.append('{');
1009     s.append(HConstants.NAME);
1010     s.append(" => '");
1011     s.append(Bytes.toString(name));
1012     s.append("'");
1013     s.append(getValues(true));
1014     s.append('}');
1015     return s.toString();
1016   }
1017 
1018   /**
1019    * @return Column family descriptor with only the customized attributes.
1020    */
1021   public String toStringCustomizedValues() {
1022     StringBuilder s = new StringBuilder();
1023     s.append('{');
1024     s.append(HConstants.NAME);
1025     s.append(" => '");
1026     s.append(Bytes.toString(name));
1027     s.append("'");
1028     s.append(getValues(false));
1029     s.append('}');
1030     return s.toString();
1031   }
1032 
1033   private StringBuilder getValues(boolean printDefaults) {
1034     StringBuilder s = new StringBuilder();
1035 
1036     boolean hasConfigKeys = false;
1037 
1038     // print all reserved keys first
1039     for (ImmutableBytesWritable k : values.keySet()) {
1040       if (!RESERVED_KEYWORDS.contains(k)) {
1041         hasConfigKeys = true;
1042         continue;
1043       }
1044       String key = Bytes.toString(k.get());
1045       String value = Bytes.toStringBinary(values.get(k).get());
1046       if (printDefaults
1047           || !DEFAULT_VALUES.containsKey(key)
1048           || !DEFAULT_VALUES.get(key).equalsIgnoreCase(value)) {
1049         s.append(", ");
1050         s.append(key);
1051         s.append(" => ");
1052         s.append('\'').append(PrettyPrinter.format(value, getUnit(key))).append('\'');
1053       }
1054     }
1055 
1056     // print all non-reserved, advanced config keys as a separate subset
1057     if (hasConfigKeys) {
1058       s.append(", ");
1059       s.append(HConstants.METADATA).append(" => ");
1060       s.append('{');
1061       boolean printComma = false;
1062       for (ImmutableBytesWritable k : values.keySet()) {
1063         if (RESERVED_KEYWORDS.contains(k)) {
1064           continue;
1065         }
1066         String key = Bytes.toString(k.get());
1067         String value = Bytes.toStringBinary(values.get(k).get());
1068         if (printComma) {
1069           s.append(", ");
1070         }
1071         printComma = true;
1072         s.append('\'').append(key).append('\'');
1073         s.append(" => ");
1074         s.append('\'').append(PrettyPrinter.format(value, getUnit(key))).append('\'');
1075       }
1076       s.append('}');
1077     }
1078 
1079     if (!configuration.isEmpty()) {
1080       s.append(", ");
1081       s.append(HConstants.CONFIGURATION).append(" => ");
1082       s.append('{');
1083       boolean printCommaForConfiguration = false;
1084       for (Map.Entry<String, String> e : configuration.entrySet()) {
1085         if (printCommaForConfiguration) s.append(", ");
1086         printCommaForConfiguration = true;
1087         s.append('\'').append(e.getKey()).append('\'');
1088         s.append(" => ");
1089         s.append('\'').append(PrettyPrinter.format(e.getValue(), getUnit(e.getKey()))).append('\'');
1090       }
1091       s.append("}");
1092     }
1093     return s;
1094   }
1095 
1096   public static Unit getUnit(String key) {
1097     Unit unit;
1098       /* TTL for now, we can add more as we neeed */
1099     if (key.equals(HColumnDescriptor.TTL)) {
1100       unit = Unit.TIME_INTERVAL;
1101     } else {
1102       unit = Unit.NONE;
1103     }
1104     return unit;
1105   }
1106 
1107   public static Map<String, String> getDefaultValues() {
1108     return Collections.unmodifiableMap(DEFAULT_VALUES);
1109   }
1110 
1111   /**
1112    * @see java.lang.Object#equals(java.lang.Object)
1113    */
1114   @Override
1115   public boolean equals(Object obj) {
1116     if (this == obj) {
1117       return true;
1118     }
1119     if (obj == null) {
1120       return false;
1121     }
1122     if (!(obj instanceof HColumnDescriptor)) {
1123       return false;
1124     }
1125     return compareTo((HColumnDescriptor)obj) == 0;
1126   }
1127 
1128   /**
1129    * @see java.lang.Object#hashCode()
1130    */
1131   @Override
1132   public int hashCode() {
1133     int result = Bytes.hashCode(this.name);
1134     result ^= Byte.valueOf(COLUMN_DESCRIPTOR_VERSION).hashCode();
1135     result ^= values.hashCode();
1136     result ^= configuration.hashCode();
1137     return result;
1138   }
1139 
1140   /**
1141    * @deprecated Writables are going away.  Use pb {@link #parseFrom(byte[])} instead.
1142    */
1143   @Deprecated
1144   public void readFields(DataInput in) throws IOException {
1145     int version = in.readByte();
1146     if (version < 6) {
1147       if (version <= 2) {
1148         Text t = new Text();
1149         t.readFields(in);
1150         this.name = t.getBytes();
1151 //        if(KeyValue.getFamilyDelimiterIndex(this.name, 0, this.name.length)
1152 //            > 0) {
1153 //          this.name = stripColon(this.name);
1154 //        }
1155       } else {
1156         this.name = Bytes.readByteArray(in);
1157       }
1158       this.values.clear();
1159       setMaxVersions(in.readInt());
1160       int ordinal = in.readInt();
1161       setCompressionType(Compression.Algorithm.values()[ordinal]);
1162       setInMemory(in.readBoolean());
1163       setBloomFilterType(in.readBoolean() ? BloomType.ROW : BloomType.NONE);
1164       if (getBloomFilterType() != BloomType.NONE && version < 5) {
1165         // If a bloomFilter is enabled and the column descriptor is less than
1166         // version 5, we need to skip over it to read the rest of the column
1167         // descriptor. There are no BloomFilterDescriptors written to disk for
1168         // column descriptors with a version number >= 5
1169         throw new UnsupportedClassVersionError(this.getClass().getName() +
1170             " does not support backward compatibility with versions older " +
1171             "than version 5");
1172       }
1173       if (version > 1) {
1174         setBlockCacheEnabled(in.readBoolean());
1175       }
1176       if (version > 2) {
1177        setTimeToLive(in.readInt());
1178       }
1179     } else {
1180       // version 6+
1181       this.name = Bytes.readByteArray(in);
1182       this.values.clear();
1183       int numValues = in.readInt();
1184       for (int i = 0; i < numValues; i++) {
1185         ImmutableBytesWritable key = new ImmutableBytesWritable();
1186         ImmutableBytesWritable value = new ImmutableBytesWritable();
1187         key.readFields(in);
1188         value.readFields(in);
1189 
1190         // in version 8, the BloomFilter setting changed from bool to enum
1191         if (version < 8 && Bytes.toString(key.get()).equals(BLOOMFILTER)) {
1192           value.set(Bytes.toBytes(
1193               Boolean.getBoolean(Bytes.toString(value.get()))
1194                 ? BloomType.ROW.toString()
1195                 : BloomType.NONE.toString()));
1196         }
1197 
1198         values.put(key, value);
1199       }
1200       if (version == 6) {
1201         // Convert old values.
1202         setValue(COMPRESSION, Compression.Algorithm.NONE.getName());
1203       }
1204       String value = getValue(HConstants.VERSIONS);
1205       this.cachedMaxVersions = (value != null)?
1206           Integer.valueOf(value).intValue(): DEFAULT_VERSIONS;
1207       if (version > 10) {
1208         configuration.clear();
1209         int numConfigs = in.readInt();
1210         for (int i = 0; i < numConfigs; i++) {
1211           ImmutableBytesWritable key = new ImmutableBytesWritable();
1212           ImmutableBytesWritable val = new ImmutableBytesWritable();
1213           key.readFields(in);
1214           val.readFields(in);
1215           configuration.put(
1216             Bytes.toString(key.get(), key.getOffset(), key.getLength()),
1217             Bytes.toString(val.get(), val.getOffset(), val.getLength()));
1218         }
1219       }
1220     }
1221   }
1222 
1223   /**
1224    * @deprecated Writables are going away.  Use {@link #toByteArray()} instead.
1225    */
1226   @Deprecated
1227   public void write(DataOutput out) throws IOException {
1228     out.writeByte(COLUMN_DESCRIPTOR_VERSION);
1229     Bytes.writeByteArray(out, this.name);
1230     out.writeInt(values.size());
1231     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
1232         values.entrySet()) {
1233       e.getKey().write(out);
1234       e.getValue().write(out);
1235     }
1236     out.writeInt(configuration.size());
1237     for (Map.Entry<String, String> e : configuration.entrySet()) {
1238       new ImmutableBytesWritable(Bytes.toBytes(e.getKey())).write(out);
1239       new ImmutableBytesWritable(Bytes.toBytes(e.getValue())).write(out);
1240     }
1241   }
1242 
1243   // Comparable
1244 
1245   public int compareTo(HColumnDescriptor o) {
1246     int result = Bytes.compareTo(this.name, o.getName());
1247     if (result == 0) {
1248       // punt on comparison for ordering, just calculate difference
1249       result = this.values.hashCode() - o.values.hashCode();
1250       if (result < 0)
1251         result = -1;
1252       else if (result > 0)
1253         result = 1;
1254     }
1255     if (result == 0) {
1256       result = this.configuration.hashCode() - o.configuration.hashCode();
1257       if (result < 0)
1258         result = -1;
1259       else if (result > 0)
1260         result = 1;
1261     }
1262     return result;
1263   }
1264 
1265   /**
1266    * @return This instance serialized with pb with pb magic prefix
1267    * @see #parseFrom(byte[])
1268    */
1269   public byte [] toByteArray() {
1270     return ProtobufUtil.prependPBMagic(convert().toByteArray());
1271   }
1272 
1273   /**
1274    * @param bytes A pb serialized {@link HColumnDescriptor} instance with pb magic prefix
1275    * @return An instance of {@link HColumnDescriptor} made from <code>bytes</code>
1276    * @throws DeserializationException
1277    * @see #toByteArray()
1278    */
1279   public static HColumnDescriptor parseFrom(final byte [] bytes) throws DeserializationException {
1280     if (!ProtobufUtil.isPBMagicPrefix(bytes)) throw new DeserializationException("No magic");
1281     int pblen = ProtobufUtil.lengthOfPBMagic();
1282     ColumnFamilySchema.Builder builder = ColumnFamilySchema.newBuilder();
1283     ColumnFamilySchema cfs = null;
1284     try {
1285       ProtobufUtil.mergeFrom(builder, bytes, pblen, bytes.length - pblen);
1286       cfs = builder.build();
1287     } catch (IOException e) {
1288       throw new DeserializationException(e);
1289     }
1290     return convert(cfs);
1291   }
1292 
1293   /**
1294    * @param cfs
1295    * @return An {@link HColumnDescriptor} made from the passed in <code>cfs</code>
1296    */
1297   public static HColumnDescriptor convert(final ColumnFamilySchema cfs) {
1298     // Use the empty constructor so we preserve the initial values set on construction for things
1299     // like maxVersion.  Otherwise, we pick up wrong values on deserialization which makes for
1300     // unrelated-looking test failures that are hard to trace back to here.
1301     HColumnDescriptor hcd = new HColumnDescriptor();
1302     hcd.name = cfs.getName().toByteArray();
1303     for (BytesBytesPair a: cfs.getAttributesList()) {
1304       hcd.setValue(a.getFirst().toByteArray(), a.getSecond().toByteArray());
1305     }
1306     for (NameStringPair a: cfs.getConfigurationList()) {
1307       hcd.setConfiguration(a.getName(), a.getValue());
1308     }
1309     return hcd;
1310   }
1311 
1312   /**
1313    * @return Convert this instance to a the pb column family type
1314    */
1315   public ColumnFamilySchema convert() {
1316     ColumnFamilySchema.Builder builder = ColumnFamilySchema.newBuilder();
1317     builder.setName(ByteStringer.wrap(getName()));
1318     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e: this.values.entrySet()) {
1319       BytesBytesPair.Builder aBuilder = BytesBytesPair.newBuilder();
1320       aBuilder.setFirst(ByteStringer.wrap(e.getKey().get()));
1321       aBuilder.setSecond(ByteStringer.wrap(e.getValue().get()));
1322       builder.addAttributes(aBuilder.build());
1323     }
1324     for (Map.Entry<String, String> e : this.configuration.entrySet()) {
1325       NameStringPair.Builder aBuilder = NameStringPair.newBuilder();
1326       aBuilder.setName(e.getKey());
1327       aBuilder.setValue(e.getValue());
1328       builder.addConfiguration(aBuilder.build());
1329     }
1330     return builder.build();
1331   }
1332 
1333   /**
1334    * Getter for accessing the configuration value by key.
1335    */
1336   public String getConfigurationValue(String key) {
1337     return configuration.get(key);
1338   }
1339 
1340   /**
1341    * Getter for fetching an unmodifiable {@link #configuration} map.
1342    */
1343   public Map<String, String> getConfiguration() {
1344     // shallow pointer copy
1345     return Collections.unmodifiableMap(configuration);
1346   }
1347 
1348   /**
1349    * Setter for storing a configuration setting in {@link #configuration} map.
1350    * @param key Config key. Same as XML config key e.g. hbase.something.or.other.
1351    * @param value String value. If null, removes the configuration.
1352    */
1353   public void setConfiguration(String key, String value) {
1354     if (value == null) {
1355       removeConfiguration(key);
1356     } else {
1357       configuration.put(key, value);
1358     }
1359   }
1360 
1361   /**
1362    * Remove a configuration setting represented by the key from the {@link #configuration} map.
1363    */
1364   public void removeConfiguration(final String key) {
1365     configuration.remove(key);
1366   }
1367 
1368   /**
1369    * Return the encryption algorithm in use by this family
1370    */
1371   @InterfaceStability.Unstable
1372   public String getEncryptionType() {
1373     return getValue(ENCRYPTION);
1374   }
1375 
1376   /**
1377    * Set the encryption algorithm for use with this family
1378    * @param algorithm
1379    */
1380   @InterfaceStability.Unstable
1381   public HColumnDescriptor setEncryptionType(String algorithm) {
1382     setValue(ENCRYPTION, algorithm);
1383     return this;
1384   }
1385 
1386   /** Return the raw crypto key attribute for the family, or null if not set  */
1387   @InterfaceStability.Unstable
1388   public byte[] getEncryptionKey() {
1389     return getValue(Bytes.toBytes(ENCRYPTION_KEY));
1390   }
1391 
1392   /** Set the raw crypto key attribute for the family */
1393   @InterfaceStability.Unstable
1394   public HColumnDescriptor setEncryptionKey(byte[] keyBytes) {
1395     setValue(Bytes.toBytes(ENCRYPTION_KEY), keyBytes);
1396     return this;
1397   }
1398 
1399   /**
1400    * @return replication factor set for this CF or {@link #DEFAULT_DFS_REPLICATION} if not set.
1401    *         <p>
1402    *         {@link #DEFAULT_DFS_REPLICATION} value indicates that user has explicitly not set any
1403    *         block replication factor for this CF, hence use the default replication factor set in
1404    *         the file system.
1405    */
1406   public short getDFSReplication() {
1407     String rf = getValue(DFS_REPLICATION);
1408     return rf == null ? DEFAULT_DFS_REPLICATION : Short.valueOf(rf);
1409   }
1410 
1411   /**
1412    * Set the replication factor to hfile(s) belonging to this family
1413    * @param replication number of replicas the blocks(s) belonging to this CF should have, or
1414    *          {@link #DEFAULT_DFS_REPLICATION} for the default replication factor set in the
1415    *          filesystem
1416    * @return this (for chained invocation)
1417    */
1418   public HColumnDescriptor setDFSReplication(short replication) {
1419     if (replication < 1 && replication != DEFAULT_DFS_REPLICATION) {
1420       throw new IllegalArgumentException(
1421           "DFS replication factor cannot be less than 1 if explictly set.");
1422     }
1423     setValue(DFS_REPLICATION, Short.toString(replication));
1424     return this;
1425   }
1426 }