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