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