View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.filter;
19  
20  import org.apache.hadoop.hbase.KeyValue;
21  import org.apache.hadoop.hbase.util.Bytes;
22  
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.io.DataInput;
26  import java.util.Arrays;
27  import java.util.Comparator;
28  import java.util.TreeSet;
29  import java.util.ArrayList;
30  
31  /**
32   * This filter is used for selecting only those keys with columns that matches
33   * a particular prefix. For example, if prefix is 'an', it will pass keys will
34   * columns like 'and', 'anti' but not keys with columns like 'ball', 'act'.
35   */
36  public class MultipleColumnPrefixFilter extends FilterBase {
37    protected byte [] hint = null;
38    protected TreeSet<byte []> sortedPrefixes = createTreeSet();
39    private final static int MAX_LOG_PREFIXES = 5;
40  
41    public MultipleColumnPrefixFilter() {
42      super();
43    }
44  
45    public MultipleColumnPrefixFilter(final byte [][] prefixes) {
46      if (prefixes != null) {
47        for (int i = 0; i < prefixes.length; i++) {
48          if (!sortedPrefixes.add(prefixes[i]))
49            throw new IllegalArgumentException ("prefixes must be distinct");
50        }
51      }
52    }
53  
54    public byte [][] getPrefix() {
55      int count = 0;
56      byte [][] temp = new byte [sortedPrefixes.size()][];
57      for (byte [] prefixes : sortedPrefixes) {
58        temp [count++] = prefixes;
59      }
60      return temp;
61    }
62  
63    @Override
64    public ReturnCode filterKeyValue(KeyValue kv) {
65      if (sortedPrefixes.size() == 0 || kv.getBuffer() == null) {
66        return ReturnCode.INCLUDE;
67      } else {
68        return filterColumn(kv.getBuffer(), kv.getQualifierOffset(), kv.getQualifierLength());
69      }
70    }
71  
72    public ReturnCode filterColumn(byte[] buffer, int qualifierOffset, int qualifierLength) {
73      byte [] qualifier = Arrays.copyOfRange(buffer, qualifierOffset,
74                                             qualifierLength + qualifierOffset);
75      TreeSet<byte []> lesserOrEqualPrefixes =
76        (TreeSet<byte []>) sortedPrefixes.headSet(qualifier, true);
77  
78      if (lesserOrEqualPrefixes.size() != 0) {
79        byte [] largestPrefixSmallerThanQualifier = lesserOrEqualPrefixes.last();
80        
81        if (Bytes.startsWith(qualifier, largestPrefixSmallerThanQualifier)) {
82          return ReturnCode.INCLUDE;
83        }
84        
85        if (lesserOrEqualPrefixes.size() == sortedPrefixes.size()) {
86          return ReturnCode.NEXT_ROW;
87        } else {
88          hint = sortedPrefixes.higher(largestPrefixSmallerThanQualifier);
89          return ReturnCode.SEEK_NEXT_USING_HINT;
90        }
91      } else {
92        hint = sortedPrefixes.first();
93        return ReturnCode.SEEK_NEXT_USING_HINT;
94      }
95    }
96  
97    public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
98      byte [][] prefixes = new byte [filterArguments.size()][];
99      for (int i = 0 ; i < filterArguments.size(); i++) {
100       byte [] columnPrefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(i));
101       prefixes[i] = columnPrefix;
102     }
103     return new MultipleColumnPrefixFilter(prefixes);
104   }
105 
106   public void write(DataOutput out) throws IOException {
107     out.writeInt(sortedPrefixes.size());
108     for (byte [] element : sortedPrefixes) {
109       Bytes.writeByteArray(out, element);
110     }
111   }
112 
113   public void readFields(DataInput in) throws IOException {
114     int x = in.readInt();
115     this.sortedPrefixes = createTreeSet();
116     for (int j = 0; j < x; j++) {
117       sortedPrefixes.add(Bytes.readByteArray(in));
118     }
119   }
120 
121   public KeyValue getNextKeyHint(KeyValue kv) {
122     return KeyValue.createFirstOnRow(
123       kv.getBuffer(), kv.getRowOffset(), kv.getRowLength(), kv.getBuffer(),
124       kv.getFamilyOffset(), kv.getFamilyLength(), hint, 0, hint.length);
125   }
126 
127   public TreeSet<byte []> createTreeSet() {
128     return new TreeSet<byte []>(new Comparator<Object>() {
129         @Override
130           public int compare (Object o1, Object o2) {
131           if (o1 == null || o2 == null)
132             throw new IllegalArgumentException ("prefixes can't be null");
133 
134           byte [] b1 = (byte []) o1;
135           byte [] b2 = (byte []) o2;
136           return Bytes.compareTo (b1, 0, b1.length, b2, 0, b2.length);
137         }
138       });
139   }
140 
141   @Override
142   public String toString() {
143     return toString(MAX_LOG_PREFIXES);
144   }
145 
146   protected String toString(int maxPrefixes) {
147     StringBuilder prefixes = new StringBuilder();
148 
149     int count = 0;
150     for (byte[] ba : this.sortedPrefixes) {
151       if (count >= maxPrefixes) {
152         break;
153       }
154       ++count;
155       prefixes.append(Bytes.toStringBinary(ba));
156       if (count < this.sortedPrefixes.size() && count < maxPrefixes) {
157         prefixes.append(", ");
158       }
159     }
160 
161     return String.format("%s (%d/%d): [%s]", this.getClass().getSimpleName(),
162         count, this.sortedPrefixes.size(), prefixes.toString());
163   }
164 }