View Javadoc

1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.rest;
22  
23  import java.io.UnsupportedEncodingException;
24  import java.net.URLDecoder;
25  import java.util.Collection;
26  import java.util.TreeSet;
27  
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  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 = HColumnDescriptor.DEFAULT_VERSIONS;
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)
110       throws IllegalArgumentException {
111     if (i >= path.length()) {
112       return i;
113     }
114     try {
115       char c;
116       StringBuilder column = new StringBuilder();
117       while (i < path.length() && (c = path.charAt(i)) != '/') {
118         if (c == ',') {
119           if (column.length() < 1) {
120             throw new IllegalArgumentException("invalid path");
121           }
122           String s = URLDecoder.decode(column.toString(),
123             HConstants.UTF8_ENCODING);
124           if (!s.contains(":")) {
125             this.columns.add(Bytes.toBytes(s + ":"));
126           } else {
127             this.columns.add(Bytes.toBytes(s));
128           }
129           column.setLength(0);
130           i++;
131           continue;
132         }
133         column.append(c);
134         i++;
135       }
136       i++;
137       // trailing list entry
138       if (column.length() > 1) {
139         String s = URLDecoder.decode(column.toString(),
140           HConstants.UTF8_ENCODING);
141         if (!s.contains(":")) {
142           this.columns.add(Bytes.toBytes(s + ":"));
143         } else {
144           this.columns.add(Bytes.toBytes(s));
145         }
146       }
147     } catch (IndexOutOfBoundsException e) {
148       throw new IllegalArgumentException(e);
149     } catch (UnsupportedEncodingException e) {
150       // shouldn't happen
151       throw new RuntimeException(e);
152     }
153     return i;
154   }
155 
156   private int parseTimestamp(final String path, int i)
157       throws IllegalArgumentException {
158     if (i >= path.length()) {
159       return i;
160     }
161     long time0 = 0, time1 = 0;
162     try {
163       char c = 0;
164       StringBuilder stamp = new StringBuilder();
165       while (i < path.length()) {
166         c = path.charAt(i);
167         if (c == '/' || c == ',') {
168           break;
169         }
170         stamp.append(c);
171         i++;
172       }
173       try {
174         time0 = Long.valueOf(URLDecoder.decode(stamp.toString(),
175           HConstants.UTF8_ENCODING));
176       } catch (NumberFormatException e) {
177         throw new IllegalArgumentException(e);
178       }
179       if (c == ',') {
180         stamp = new StringBuilder();
181         i++;
182         while (i < path.length() && ((c = path.charAt(i)) != '/')) {
183           stamp.append(c);
184           i++;
185         }
186         try {
187           time1 = Long.valueOf(URLDecoder.decode(stamp.toString(),
188             HConstants.UTF8_ENCODING));
189         } catch (NumberFormatException e) {
190           throw new IllegalArgumentException(e);
191         }
192       }
193       if (c == '/') {
194         i++;
195       }
196     } catch (IndexOutOfBoundsException e) {
197       throw new IllegalArgumentException(e);
198     } catch (UnsupportedEncodingException e) {
199       // shouldn't happen
200       throw new RuntimeException(e);
201     }
202     if (time1 != 0) {
203       startTime = time0;
204       endTime = time1;
205     } else {
206       endTime = time0;
207     }
208     return i;
209   }
210 
211   private int parseQueryParams(final String path, int i) {
212     if (i >= path.length()) {
213       return i;
214     }
215     StringBuilder query = new StringBuilder();
216     try {
217       query.append(URLDecoder.decode(path.substring(i), 
218         HConstants.UTF8_ENCODING));
219     } catch (UnsupportedEncodingException e) {
220       // should not happen
221       throw new RuntimeException(e);
222     }
223     i += query.length();
224     int j = 0;
225     while (j < query.length()) {
226       char c = query.charAt(j);
227       if (c != '?' && c != '&') {
228         break;
229       }
230       if (++j > query.length()) {
231         throw new IllegalArgumentException("malformed query parameter");
232       }
233       char what = query.charAt(j);
234       if (++j > query.length()) {
235         break;
236       }
237       c = query.charAt(j);
238       if (c != '=') {
239         throw new IllegalArgumentException("malformed query parameter");
240       }
241       if (++j > query.length()) {
242         break;
243       }
244       switch (what) {
245       case 'm': {
246         StringBuilder sb = new StringBuilder();
247         while (j <= query.length()) {
248           c = query.charAt(j);
249           if (c < '0' || c > '9') {
250             j--;
251             break;
252           }
253           sb.append(c);
254         }
255         maxVersions = Integer.valueOf(sb.toString());
256       } break;
257       case 'n': {
258         StringBuilder sb = new StringBuilder();
259         while (j <= query.length()) {
260           c = query.charAt(j);
261           if (c < '0' || c > '9') {
262             j--;
263             break;
264           }
265           sb.append(c);
266         }
267         maxValues = Integer.valueOf(sb.toString());
268       } break;
269       default:
270         throw new IllegalArgumentException("unknown parameter '" + c + "'");
271       }
272     }
273     return i;
274   }
275 
276   public RowSpec(byte[] startRow, byte[] endRow, byte[][] columns,
277       long startTime, long endTime, int maxVersions) {
278     this.row = startRow;
279     this.endRow = endRow;
280     if (columns != null) {
281       for (byte[] col: columns) {
282         this.columns.add(col);
283       }
284     }
285     this.startTime = startTime;
286     this.endTime = endTime;
287     this.maxVersions = maxVersions;
288   }
289 
290   public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns,
291       long startTime, long endTime, int maxVersions) {
292     this.row = startRow;
293     this.endRow = endRow;
294     if (columns != null) {
295       this.columns.addAll(columns);
296     }
297     this.startTime = startTime;
298     this.endTime = endTime;
299     this.maxVersions = maxVersions;
300   }
301 
302   public boolean isSingleRow() {
303     return endRow == null;
304   }
305 
306   public int getMaxVersions() {
307     return maxVersions;
308   }
309 
310   public void setMaxVersions(final int maxVersions) {
311     this.maxVersions = maxVersions;
312   }
313 
314   public int getMaxValues() {
315     return maxValues;
316   }
317 
318   public void setMaxValues(final int maxValues) {
319     this.maxValues = maxValues;
320   }
321 
322   public boolean hasColumns() {
323     return !columns.isEmpty();
324   }
325 
326   public byte[] getRow() {
327     return row;
328   }
329 
330   public byte[] getStartRow() {
331     return row;
332   }
333 
334   public boolean hasEndRow() {
335     return endRow != null;
336   }
337 
338   public byte[] getEndRow() {
339     return endRow;
340   }
341 
342   public void addColumn(final byte[] column) {
343     columns.add(column);
344   }
345 
346   public byte[][] getColumns() {
347     return columns.toArray(new byte[columns.size()][]);
348   }
349 
350   public boolean hasTimestamp() {
351     return (startTime == 0) && (endTime != Long.MAX_VALUE);
352   }
353 
354   public long getTimestamp() {
355     return endTime;
356   }
357 
358   public long getStartTime() {
359     return startTime;
360   }
361 
362   public void setStartTime(final long startTime) {
363     this.startTime = startTime;
364   }
365 
366   public long getEndTime() {
367     return endTime;
368   }
369 
370   public void setEndTime(long endTime) {
371     this.endTime = endTime;
372   }
373 
374   public String toString() {
375     StringBuilder result = new StringBuilder();
376     result.append("{startRow => '");
377     if (row != null) {
378       result.append(Bytes.toString(row));
379     }
380     result.append("', endRow => '");
381     if (endRow != null)  {
382       result.append(Bytes.toString(endRow));
383     }
384     result.append("', columns => [");
385     for (byte[] col: columns) {
386       result.append(" '");
387       result.append(Bytes.toString(col));
388       result.append("'");
389     }
390     result.append(" ], startTime => ");
391     result.append(Long.toString(startTime));
392     result.append(", endTime => ");
393     result.append(Long.toString(endTime));
394     result.append(", maxVersions => ");
395     result.append(Integer.toString(maxVersions));
396     result.append(", maxValues => ");
397     result.append(Integer.toString(maxValues));
398     result.append("}");
399     return result.toString();
400   }
401 }