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.util.Collection;
24  import java.util.TreeSet;
25  
26  import org.apache.hadoop.hbase.HColumnDescriptor;
27  import org.apache.hadoop.hbase.HConstants;
28  import org.apache.hadoop.hbase.util.Bytes;
29  
30  /**
31   * Parses a path based row/column/timestamp specification into its component
32   * elements.
33   * <p>
34   *  
35   */
36  public class RowSpec {
37    public static final long DEFAULT_START_TIMESTAMP = 0;
38    public static final long DEFAULT_END_TIMESTAMP = Long.MAX_VALUE;
39    
40    private byte[] row = HConstants.EMPTY_START_ROW;
41    private byte[] endRow = null;
42    private TreeSet<byte[]> columns =
43      new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
44    private long startTime = DEFAULT_START_TIMESTAMP;
45    private long endTime = DEFAULT_END_TIMESTAMP;
46    private int maxVersions = HColumnDescriptor.DEFAULT_VERSIONS;
47    private int maxValues = Integer.MAX_VALUE;
48  
49    public RowSpec(String path) throws IllegalArgumentException {
50      int i = 0;
51      while (path.charAt(i) == '/') {
52        i++;
53      }
54      i = parseRowKeys(path, i);
55      i = parseColumns(path, i);
56      i = parseTimestamp(path, i);
57      i = parseQueryParams(path, i);
58    }
59  
60    private int parseRowKeys(final String path, int i)
61        throws IllegalArgumentException {
62      StringBuilder startRow = new StringBuilder();
63      StringBuilder endRow = null;
64      try {
65        char c;
66        boolean doEndRow = false;
67        while (i < path.length() && (c = path.charAt(i)) != '/') {
68          if (c == ',') {
69            doEndRow = true;
70            i++;
71            break;
72          }
73          startRow.append(c);
74          i++;
75        }
76        i++;
77        this.row = Bytes.toBytes(startRow.toString());
78        if (doEndRow) {
79          endRow = new StringBuilder();
80          while ((c = path.charAt(i)) != '/') {
81            endRow.append(c);
82            i++;
83          }
84          i++;
85        }
86      } catch (IndexOutOfBoundsException e) {
87        throw new IllegalArgumentException(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       boolean hasColon = false;
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           if (!hasColon) {
125             column.append(':');
126           }
127           this.columns.add(Bytes.toBytes(column.toString()));
128           column = new StringBuilder();
129           hasColon = false;
130           i++;
131           continue;
132         }
133         if (c == ':') {
134           hasColon = true;
135         }
136         column.append(c);
137         i++;
138       }
139       i++;
140       // trailing list entry
141       if (column.length() > 1) {
142         if (!hasColon) {
143           column.append(':');
144         }
145         this.columns.add(Bytes.toBytes(column.toString()));
146       }
147     } catch (IndexOutOfBoundsException e) {
148       throw new IllegalArgumentException(e);
149     }
150     return i;
151   }
152 
153   private int parseTimestamp(final String path, int i)
154       throws IllegalArgumentException {
155     if (i >= path.length()) {
156       return i;
157     }
158     long time0 = 0, time1 = 0;
159     try {
160       char c = 0;
161       StringBuilder stamp = new StringBuilder();
162       while (i < path.length()) {
163         c = path.charAt(i);
164         if (c == '/' || c == ',') {
165           break;
166         }
167         stamp.append(c);
168         i++;
169       }
170       try {
171         time0 = Long.valueOf(stamp.toString());
172       } catch (NumberFormatException e) {
173         throw new IllegalArgumentException(e);
174       }
175       if (c == ',') {
176         stamp = new StringBuilder();
177         i++;
178         while (i < path.length() && ((c = path.charAt(i)) != '/')) {
179           stamp.append(c);
180           i++;
181         }
182         try {
183           time1 = Long.valueOf(stamp.toString());
184         } catch (NumberFormatException e) {
185           throw new IllegalArgumentException(e);
186         }
187       }
188       if (c == '/') {
189         i++;
190       }
191     } catch (IndexOutOfBoundsException e) {
192       throw new IllegalArgumentException(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     while (i < path.length()) {
205       char c = path.charAt(i);
206       if (c != '?' && c != '&') {
207         break;
208       }
209       if (++i > path.length()) {
210         break;
211       }
212       char what = path.charAt(i);
213       if (++i > path.length()) {
214         break;
215       }
216       c = path.charAt(i);
217       if (c != '=') {
218         throw new IllegalArgumentException("malformed query parameter");
219       }
220       if (++i > path.length()) {
221         break;
222       }
223       switch (what) {
224       case 'm': {
225         StringBuilder sb = new StringBuilder();
226         while (i <= path.length()) {
227           c = path.charAt(i);
228           if (c < '0' || c > '9') {
229             i--;
230             break;
231           }
232           sb.append(c);
233         }
234         maxVersions = Integer.valueOf(sb.toString());
235       } break;
236       case 'n': {
237         StringBuilder sb = new StringBuilder();
238         while (i <= path.length()) {
239           c = path.charAt(i);
240           if (c < '0' || c > '9') {
241             i--;
242             break;
243           }
244           sb.append(c);
245         }
246         maxValues = Integer.valueOf(sb.toString());
247       } break;
248       default:
249         throw new IllegalArgumentException("unknown parameter '" + c + "'");
250       }
251     }
252     return i;
253   }
254 
255   public RowSpec(byte[] startRow, byte[] endRow, byte[][] columns,
256       long startTime, long endTime, int maxVersions) {
257     this.row = startRow;
258     this.endRow = endRow;
259     if (columns != null) {
260       for (byte[] col: columns) {
261         this.columns.add(col);
262       }
263     }
264     this.startTime = startTime;
265     this.endTime = endTime;
266     this.maxVersions = maxVersions;
267   }
268 
269   public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns,
270       long startTime, long endTime, int maxVersions) {
271     this.row = startRow;
272     this.endRow = endRow;
273     if (columns != null) {
274       this.columns.addAll(columns);
275     }
276     this.startTime = startTime;
277     this.endTime = endTime;
278     this.maxVersions = maxVersions;
279   }
280 
281   public boolean isSingleRow() {
282     return endRow == null;
283   }
284 
285   public int getMaxVersions() {
286     return maxVersions;
287   }
288 
289   public void setMaxVersions(final int maxVersions) {
290     this.maxVersions = maxVersions;
291   }
292 
293   public int getMaxValues() {
294     return maxValues;
295   }
296 
297   public void setMaxValues(final int maxValues) {
298     this.maxValues = maxValues;
299   }
300 
301   public boolean hasColumns() {
302     return !columns.isEmpty();
303   }
304 
305   public byte[] getRow() {
306     return row;
307   }
308 
309   public byte[] getStartRow() {
310     return row;
311   }
312 
313   public boolean hasEndRow() {
314     return endRow != null;
315   }
316 
317   public byte[] getEndRow() {
318     return endRow;
319   }
320 
321   public void addColumn(final byte[] column) {
322     columns.add(column);
323   }
324 
325   public byte[][] getColumns() {
326     return columns.toArray(new byte[columns.size()][]);
327   }
328 
329   public boolean hasTimestamp() {
330     return (startTime == 0) && (endTime != Long.MAX_VALUE);
331   }
332 
333   public long getTimestamp() {
334     return endTime;
335   }
336 
337   public long getStartTime() {
338     return startTime;
339   }
340 
341   public void setStartTime(final long startTime) {
342     this.startTime = startTime;
343   }
344 
345   public long getEndTime() {
346     return endTime;
347   }
348 
349   public void setEndTime(long endTime) {
350     this.endTime = endTime;
351   }
352 
353   public String toString() {
354     StringBuilder result = new StringBuilder();
355     result.append("{startRow => '");
356     if (row != null) {
357       result.append(Bytes.toString(row));
358     }
359     result.append("', endRow => '");
360     if (endRow != null)  {
361       result.append(Bytes.toString(endRow));
362     }
363     result.append("', columns => [");
364     for (byte[] col: columns) {
365       result.append(" '");
366       result.append(Bytes.toString(col));
367       result.append("'");
368     }
369     result.append(" ], startTime => ");
370     result.append(Long.toString(startTime));
371     result.append(", endTime => ");
372     result.append(Long.toString(endTime));
373     result.append(", maxVersions => ");
374     result.append(Integer.toString(maxVersions));
375     result.append(", maxValues => ");
376     result.append(Integer.toString(maxValues));
377     result.append("}");
378     return result.toString();
379   }
380 }