/* * Copyright 2010 The Apache Software Foundation * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.rest; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Collection; import java.util.TreeSet; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.util.Bytes; /** * Parses a path based row/column/timestamp specification into its component * elements. * <p> * */ public class RowSpec { public static final long DEFAULT_START_TIMESTAMP = 0; public static final long DEFAULT_END_TIMESTAMP = Long.MAX_VALUE; private byte[] row = HConstants.EMPTY_START_ROW; private byte[] endRow = null; private TreeSet<byte[]> columns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR); private long startTime = DEFAULT_START_TIMESTAMP; private long endTime = DEFAULT_END_TIMESTAMP; private int maxVersions = HColumnDescriptor.DEFAULT_VERSIONS; private int maxValues = Integer.MAX_VALUE; public RowSpec(String path) throws IllegalArgumentException { int i = 0; while (path.charAt(i) == '/') { i++; } i = parseRowKeys(path, i); i = parseColumns(path, i); i = parseTimestamp(path, i); i = parseQueryParams(path, i); } private int parseRowKeys(final String path, int i) throws IllegalArgumentException { String startRow = null, endRow = null; try { StringBuilder sb = new StringBuilder(); char c; while (i < path.length() && (c = path.charAt(i)) != '/') { sb.append(c); i++; } i++; startRow = sb.toString(); int idx = startRow.indexOf(','); if (idx != -1) { startRow = URLDecoder.decode(startRow.substring(0, idx), HConstants.UTF8_ENCODING); endRow = URLDecoder.decode(startRow.substring(idx + 1), HConstants.UTF8_ENCODING); } else { startRow = URLDecoder.decode(startRow, HConstants.UTF8_ENCODING); } } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } // HBase does not support wildcards on row keys so we will emulate a // suffix glob by synthesizing appropriate start and end row keys for // table scanning if (startRow.charAt(startRow.length() - 1) == '*') { if (endRow != null) throw new IllegalArgumentException("invalid path: start row "+ "specified with wildcard"); this.row = Bytes.toBytes(startRow.substring(0, startRow.lastIndexOf("*"))); this.endRow = new byte[this.row.length + 1]; System.arraycopy(this.row, 0, this.endRow, 0, this.row.length); this.endRow[this.row.length] = (byte)255; } else { this.row = Bytes.toBytes(startRow.toString()); if (endRow != null) { this.endRow = Bytes.toBytes(endRow.toString()); } } return i; } private int parseColumns(final String path, int i) throws IllegalArgumentException { if (i >= path.length()) { return i; } try { char c; StringBuilder column = new StringBuilder(); while (i < path.length() && (c = path.charAt(i)) != '/') { if (c == ',') { if (column.length() < 1) { throw new IllegalArgumentException("invalid path"); } String s = URLDecoder.decode(column.toString(), HConstants.UTF8_ENCODING); if (!s.contains(":")) { this.columns.add(Bytes.toBytes(s + ":")); } else { this.columns.add(Bytes.toBytes(s)); } column.setLength(0); i++; continue; } column.append(c); i++; } i++; // trailing list entry if (column.length() > 1) { String s = URLDecoder.decode(column.toString(), HConstants.UTF8_ENCODING); if (!s.contains(":")) { this.columns.add(Bytes.toBytes(s + ":")); } else { this.columns.add(Bytes.toBytes(s)); } } } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException(e); } catch (UnsupportedEncodingException e) { // shouldn't happen throw new RuntimeException(e); } return i; } private int parseTimestamp(final String path, int i) throws IllegalArgumentException { if (i >= path.length()) { return i; } long time0 = 0, time1 = 0; try { char c = 0; StringBuilder stamp = new StringBuilder(); while (i < path.length()) { c = path.charAt(i); if (c == '/' || c == ',') { break; } stamp.append(c); i++; } try { time0 = Long.valueOf(URLDecoder.decode(stamp.toString(), HConstants.UTF8_ENCODING)); } catch (NumberFormatException e) { throw new IllegalArgumentException(e); } if (c == ',') { stamp = new StringBuilder(); i++; while (i < path.length() && ((c = path.charAt(i)) != '/')) { stamp.append(c); i++; } try { time1 = Long.valueOf(URLDecoder.decode(stamp.toString(), HConstants.UTF8_ENCODING)); } catch (NumberFormatException e) { throw new IllegalArgumentException(e); } } if (c == '/') { i++; } } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException(e); } catch (UnsupportedEncodingException e) { // shouldn't happen throw new RuntimeException(e); } if (time1 != 0) { startTime = time0; endTime = time1; } else { endTime = time0; } return i; } private int parseQueryParams(final String path, int i) { if (i >= path.length()) { return i; } StringBuilder query = new StringBuilder(); try { query.append(URLDecoder.decode(path.substring(i), HConstants.UTF8_ENCODING)); } catch (UnsupportedEncodingException e) { // should not happen throw new RuntimeException(e); } i += query.length(); int j = 0; while (j < query.length()) { char c = query.charAt(j); if (c != '?' && c != '&') { break; } if (++j > query.length()) { throw new IllegalArgumentException("malformed query parameter"); } char what = query.charAt(j); if (++j > query.length()) { break; } c = query.charAt(j); if (c != '=') { throw new IllegalArgumentException("malformed query parameter"); } if (++j > query.length()) { break; } switch (what) { case 'm': { StringBuilder sb = new StringBuilder(); while (j <= query.length()) { c = query.charAt(i); if (c < '0' || c > '9') { j--; break; } sb.append(c); } maxVersions = Integer.valueOf(sb.toString()); } break; case 'n': { StringBuilder sb = new StringBuilder(); while (j <= query.length()) { c = query.charAt(i); if (c < '0' || c > '9') { j--; break; } sb.append(c); } maxValues = Integer.valueOf(sb.toString()); } break; default: throw new IllegalArgumentException("unknown parameter '" + c + "'"); } } return i; } public RowSpec(byte[] startRow, byte[] endRow, byte[][] columns, long startTime, long endTime, int maxVersions) { this.row = startRow; this.endRow = endRow; if (columns != null) { for (byte[] col: columns) { this.columns.add(col); } } this.startTime = startTime; this.endTime = endTime; this.maxVersions = maxVersions; } public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns, long startTime, long endTime, int maxVersions) { this.row = startRow; this.endRow = endRow; if (columns != null) { this.columns.addAll(columns); } this.startTime = startTime; this.endTime = endTime; this.maxVersions = maxVersions; } public boolean isSingleRow() { return endRow == null; } public int getMaxVersions() { return maxVersions; } public void setMaxVersions(final int maxVersions) { this.maxVersions = maxVersions; } public int getMaxValues() { return maxValues; } public void setMaxValues(final int maxValues) { this.maxValues = maxValues; } public boolean hasColumns() { return !columns.isEmpty(); } public byte[] getRow() { return row; } public byte[] getStartRow() { return row; } public boolean hasEndRow() { return endRow != null; } public byte[] getEndRow() { return endRow; } public void addColumn(final byte[] column) { columns.add(column); } public byte[][] getColumns() { return columns.toArray(new byte[columns.size()][]); } public boolean hasTimestamp() { return (startTime == 0) && (endTime != Long.MAX_VALUE); } public long getTimestamp() { return endTime; } public long getStartTime() { return startTime; } public void setStartTime(final long startTime) { this.startTime = startTime; } public long getEndTime() { return endTime; } public void setEndTime(long endTime) { this.endTime = endTime; } public String toString() { StringBuilder result = new StringBuilder(); result.append("{startRow => '"); if (row != null) { result.append(Bytes.toString(row)); } result.append("', endRow => '"); if (endRow != null) { result.append(Bytes.toString(endRow)); } result.append("', columns => ["); for (byte[] col: columns) { result.append(" '"); result.append(Bytes.toString(col)); result.append("'"); } result.append(" ], startTime => "); result.append(Long.toString(startTime)); result.append(", endTime => "); result.append(Long.toString(endTime)); result.append(", maxVersions => "); result.append(Integer.toString(maxVersions)); result.append(", maxValues => "); result.append(Integer.toString(maxValues)); result.append("}"); return result.toString(); } }