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  
20  package org.apache.hadoop.hbase.rest;
21  
22  import java.io.UnsupportedEncodingException;
23  import java.net.URLDecoder;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.TreeSet;
29  
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.hbase.HConstants;
32  import org.apache.hadoop.hbase.util.Bytes;
33  
34  /**
35   * Parses a path based row/column/timestamp specification into its component
36   * elements.
37   * <p>
38   *  
39   */
40  @InterfaceAudience.Private
41  public class RowSpec {
42    public static final long DEFAULT_START_TIMESTAMP = 0;
43    public static final long DEFAULT_END_TIMESTAMP = Long.MAX_VALUE;
44    
45    private byte[] row = HConstants.EMPTY_START_ROW;
46    private byte[] endRow = null;
47    private TreeSet<byte[]> columns =
48      new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
49    private List<String> labels = new ArrayList<String>();  
50    private long startTime = DEFAULT_START_TIMESTAMP;
51    private long endTime = DEFAULT_END_TIMESTAMP;
52    private int maxVersions = 1;
53    private int maxValues = Integer.MAX_VALUE;
54  
55    public RowSpec(String path) throws IllegalArgumentException {
56      int i = 0;
57      while (path.charAt(i) == '/') {
58        i++;
59      }
60      i = parseRowKeys(path, i);
61      i = parseColumns(path, i);
62      i = parseTimestamp(path, i);
63      i = parseQueryParams(path, i);
64    }
65  
66    private int parseRowKeys(final String path, int i)
67        throws IllegalArgumentException {
68      String startRow = null, endRow = null;
69      try {
70        StringBuilder sb = new StringBuilder();
71        char c;
72        while (i < path.length() && (c = path.charAt(i)) != '/') {
73          sb.append(c);
74          i++;
75        }
76        i++;
77        String row = startRow = sb.toString();
78        int idx = startRow.indexOf(',');
79        if (idx != -1) {
80          startRow = URLDecoder.decode(row.substring(0, idx),
81            HConstants.UTF8_ENCODING);
82          endRow = URLDecoder.decode(row.substring(idx + 1),
83            HConstants.UTF8_ENCODING);
84        } else {
85          startRow = URLDecoder.decode(row, HConstants.UTF8_ENCODING);
86        }
87      } catch (IndexOutOfBoundsException e) {
88        throw new IllegalArgumentException(e);
89      } catch (UnsupportedEncodingException e) {
90        throw new RuntimeException(e);
91      }
92      // HBase does not support wildcards on row keys so we will emulate a
93      // suffix glob by synthesizing appropriate start and end row keys for
94      // table scanning
95      if (startRow.charAt(startRow.length() - 1) == '*') {
96        if (endRow != null)
97          throw new IllegalArgumentException("invalid path: start row "+
98            "specified with wildcard");
99        this.row = Bytes.toBytes(startRow.substring(0, 
100         startRow.lastIndexOf("*")));
101       this.endRow = new byte[this.row.length + 1];
102       System.arraycopy(this.row, 0, this.endRow, 0, this.row.length);
103       this.endRow[this.row.length] = (byte)255;
104     } else {
105       this.row = Bytes.toBytes(startRow.toString());
106       if (endRow != null) {
107         this.endRow = Bytes.toBytes(endRow.toString());
108       }
109     }
110     return i;
111   }
112 
113   private int parseColumns(final String path, int i) throws IllegalArgumentException {
114     if (i >= path.length()) {
115       return i;
116     }
117     try {
118       char c;
119       StringBuilder column = new StringBuilder();
120       while (i < path.length() && (c = path.charAt(i)) != '/') {
121         if (c == ',') {
122           if (column.length() < 1) {
123             throw new IllegalArgumentException("invalid path");
124           }
125           String s = URLDecoder.decode(column.toString(), HConstants.UTF8_ENCODING);
126           this.columns.add(Bytes.toBytes(s));
127           column.setLength(0);
128           i++;
129           continue;
130         }
131         column.append(c);
132         i++;
133       }
134       i++;
135       // trailing list entry
136       if (column.length() > 0) {
137         String s = URLDecoder.decode(column.toString(), HConstants.UTF8_ENCODING);
138         this.columns.add(Bytes.toBytes(s));
139       }
140     } catch (IndexOutOfBoundsException e) {
141       throw new IllegalArgumentException(e);
142     } catch (UnsupportedEncodingException e) {
143       // shouldn't happen
144       throw new RuntimeException(e);
145     }
146     return i;
147   }
148 
149   private int parseTimestamp(final String path, int i)
150       throws IllegalArgumentException {
151     if (i >= path.length()) {
152       return i;
153     }
154     long time0 = 0, time1 = 0;
155     try {
156       char c = 0;
157       StringBuilder stamp = new StringBuilder();
158       while (i < path.length()) {
159         c = path.charAt(i);
160         if (c == '/' || c == ',') {
161           break;
162         }
163         stamp.append(c);
164         i++;
165       }
166       try {
167         time0 = Long.valueOf(URLDecoder.decode(stamp.toString(),
168           HConstants.UTF8_ENCODING));
169       } catch (NumberFormatException e) {
170         throw new IllegalArgumentException(e);
171       }
172       if (c == ',') {
173         stamp = new StringBuilder();
174         i++;
175         while (i < path.length() && ((c = path.charAt(i)) != '/')) {
176           stamp.append(c);
177           i++;
178         }
179         try {
180           time1 = Long.valueOf(URLDecoder.decode(stamp.toString(),
181             HConstants.UTF8_ENCODING));
182         } catch (NumberFormatException e) {
183           throw new IllegalArgumentException(e);
184         }
185       }
186       if (c == '/') {
187         i++;
188       }
189     } catch (IndexOutOfBoundsException e) {
190       throw new IllegalArgumentException(e);
191     } catch (UnsupportedEncodingException e) {
192       // shouldn't happen
193       throw new RuntimeException(e);
194     }
195     if (time1 != 0) {
196       startTime = time0;
197       endTime = time1;
198     } else {
199       endTime = time0;
200     }
201     return i;
202   }
203 
204   private int parseQueryParams(final String path, int i) {
205     if (i >= path.length()) {
206       return i;
207     }
208     StringBuilder query = new StringBuilder();
209     try {
210       query.append(URLDecoder.decode(path.substring(i), 
211         HConstants.UTF8_ENCODING));
212     } catch (UnsupportedEncodingException e) {
213       // should not happen
214       throw new RuntimeException(e);
215     }
216     i += query.length();
217     int j = 0;
218     while (j < query.length()) {
219       char c = query.charAt(j);
220       if (c != '?' && c != '&') {
221         break;
222       }
223       if (++j > query.length()) {
224         throw new IllegalArgumentException("malformed query parameter");
225       }
226       char what = query.charAt(j);
227       if (++j > query.length()) {
228         break;
229       }
230       c = query.charAt(j);
231       if (c != '=') {
232         throw new IllegalArgumentException("malformed query parameter");
233       }
234       if (++j > query.length()) {
235         break;
236       }
237       switch (what) {
238       case 'm': {
239         StringBuilder sb = new StringBuilder();
240         while (j <= query.length()) {
241           c = query.charAt(j);
242           if (c < '0' || c > '9') {
243             j--;
244             break;
245           }
246           sb.append(c);
247         }
248         maxVersions = Integer.valueOf(sb.toString());
249       } break;
250       case 'n': {
251         StringBuilder sb = new StringBuilder();
252         while (j <= query.length()) {
253           c = query.charAt(j);
254           if (c < '0' || c > '9') {
255             j--;
256             break;
257           }
258           sb.append(c);
259         }
260         maxValues = Integer.valueOf(sb.toString());
261       } break;
262       default:
263         throw new IllegalArgumentException("unknown parameter '" + c + "'");
264       }
265     }
266     return i;
267   }
268 
269   public RowSpec(byte[] startRow, byte[] endRow, byte[][] columns,
270       long startTime, long endTime, int maxVersions) {
271     this.row = startRow;
272     this.endRow = endRow;
273     if (columns != null) {
274       Collections.addAll(this.columns, columns);
275     }
276     this.startTime = startTime;
277     this.endTime = endTime;
278     this.maxVersions = maxVersions;
279   }
280 
281   public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns,
282       long startTime, long endTime, int maxVersions, Collection<String> labels) {
283     this(startRow, endRow, columns, startTime, endTime, maxVersions);
284     if(labels != null) {
285       this.labels.addAll(labels);
286     }
287   }
288   public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns,
289       long startTime, long endTime, int maxVersions) {
290     this.row = startRow;
291     this.endRow = endRow;
292     if (columns != null) {
293       this.columns.addAll(columns);
294     }
295     this.startTime = startTime;
296     this.endTime = endTime;
297     this.maxVersions = maxVersions;
298   }
299 
300   public boolean isSingleRow() {
301     return endRow == null;
302   }
303 
304   public int getMaxVersions() {
305     return maxVersions;
306   }
307 
308   public void setMaxVersions(final int maxVersions) {
309     this.maxVersions = maxVersions;
310   }
311 
312   public int getMaxValues() {
313     return maxValues;
314   }
315 
316   public void setMaxValues(final int maxValues) {
317     this.maxValues = maxValues;
318   }
319 
320   public boolean hasColumns() {
321     return !columns.isEmpty();
322   }
323   
324   public boolean hasLabels() {
325     return !labels.isEmpty();
326   }
327 
328   public byte[] getRow() {
329     return row;
330   }
331 
332   public byte[] getStartRow() {
333     return row;
334   }
335 
336   public boolean hasEndRow() {
337     return endRow != null;
338   }
339 
340   public byte[] getEndRow() {
341     return endRow;
342   }
343 
344   public void addColumn(final byte[] column) {
345     columns.add(column);
346   }
347 
348   public byte[][] getColumns() {
349     return columns.toArray(new byte[columns.size()][]);
350   }
351   
352   public List<String> getLabels() {
353     return labels;
354   }
355 
356   public boolean hasTimestamp() {
357     return (startTime == 0) && (endTime != Long.MAX_VALUE);
358   }
359 
360   public long getTimestamp() {
361     return endTime;
362   }
363 
364   public long getStartTime() {
365     return startTime;
366   }
367 
368   public void setStartTime(final long startTime) {
369     this.startTime = startTime;
370   }
371 
372   public long getEndTime() {
373     return endTime;
374   }
375 
376   public void setEndTime(long endTime) {
377     this.endTime = endTime;
378   }
379 
380   public String toString() {
381     StringBuilder result = new StringBuilder();
382     result.append("{startRow => '");
383     if (row != null) {
384       result.append(Bytes.toString(row));
385     }
386     result.append("', endRow => '");
387     if (endRow != null)  {
388       result.append(Bytes.toString(endRow));
389     }
390     result.append("', columns => [");
391     for (byte[] col: columns) {
392       result.append(" '");
393       result.append(Bytes.toString(col));
394       result.append("'");
395     }
396     result.append(" ], startTime => ");
397     result.append(Long.toString(startTime));
398     result.append(", endTime => ");
399     result.append(Long.toString(endTime));
400     result.append(", maxVersions => ");
401     result.append(Integer.toString(maxVersions));
402     result.append(", maxValues => ");
403     result.append(Integer.toString(maxValues));
404     result.append("}");
405     return result.toString();
406   }
407 }