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