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