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