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