001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.rest; 019 020import java.io.UnsupportedEncodingException; 021import java.net.URLDecoder; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.List; 026import java.util.TreeSet; 027import org.apache.hadoop.hbase.HConstants; 028import org.apache.hadoop.hbase.util.Bytes; 029import org.apache.yetus.audience.InterfaceAudience; 030 031/** 032 * Parses a path based row/column/timestamp specification into its component elements. 033 * <p> 034 */ 035@InterfaceAudience.Private 036public class RowSpec { 037 public static final long DEFAULT_START_TIMESTAMP = 0; 038 public static final long DEFAULT_END_TIMESTAMP = Long.MAX_VALUE; 039 040 private byte[] row = HConstants.EMPTY_START_ROW; 041 private byte[] endRow = null; 042 private TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR); 043 private List<String> labels = new ArrayList<>(); 044 private long startTime = DEFAULT_START_TIMESTAMP; 045 private long endTime = DEFAULT_END_TIMESTAMP; 046 private int maxVersions = 1; 047 private int maxValues = Integer.MAX_VALUE; 048 049 public RowSpec(String path) throws IllegalArgumentException { 050 int i = 0; 051 while (path.charAt(i) == '/') { 052 i++; 053 } 054 i = parseRowKeys(path, i); 055 i = parseColumns(path, i); 056 i = parseTimestamp(path, i); 057 i = parseQueryParams(path, i); 058 } 059 060 private int parseRowKeys(final String path, int i) throws IllegalArgumentException { 061 String startRow = null, endRow = null; 062 try { 063 StringBuilder sb = new StringBuilder(); 064 char c; 065 while (i < path.length() && (c = path.charAt(i)) != '/') { 066 sb.append(c); 067 i++; 068 } 069 i++; 070 String row = startRow = sb.toString(); 071 int idx = startRow.indexOf(','); 072 if (idx != -1) { 073 startRow = URLDecoder.decode(row.substring(0, idx), HConstants.UTF8_ENCODING); 074 endRow = URLDecoder.decode(row.substring(idx + 1), HConstants.UTF8_ENCODING); 075 } else { 076 startRow = URLDecoder.decode(row, HConstants.UTF8_ENCODING); 077 } 078 } catch (IndexOutOfBoundsException e) { 079 throw new IllegalArgumentException(e); 080 } catch (UnsupportedEncodingException e) { 081 throw new RuntimeException(e); 082 } 083 // HBase does not support wildcards on row keys so we will emulate a 084 // suffix glob by synthesizing appropriate start and end row keys for 085 // table scanning 086 if (startRow.charAt(startRow.length() - 1) == '*') { 087 if (endRow != null) 088 throw new IllegalArgumentException("invalid path: start row " + "specified with wildcard"); 089 this.row = Bytes.toBytes(startRow.substring(0, startRow.lastIndexOf("*"))); 090 this.endRow = new byte[this.row.length + 1]; 091 System.arraycopy(this.row, 0, this.endRow, 0, this.row.length); 092 this.endRow[this.row.length] = (byte) 255; 093 } else { 094 this.row = Bytes.toBytes(startRow.toString()); 095 if (endRow != null) { 096 this.endRow = Bytes.toBytes(endRow.toString()); 097 } 098 } 099 return i; 100 } 101 102 private int parseColumns(final String path, int i) throws IllegalArgumentException { 103 if (i >= path.length()) { 104 return i; 105 } 106 try { 107 char c; 108 StringBuilder column = new StringBuilder(); 109 while (i < path.length() && (c = path.charAt(i)) != '/') { 110 if (c == ',') { 111 if (column.length() < 1) { 112 throw new IllegalArgumentException("invalid path"); 113 } 114 String s = URLDecoder.decode(column.toString(), HConstants.UTF8_ENCODING); 115 this.columns.add(Bytes.toBytes(s)); 116 column.setLength(0); 117 i++; 118 continue; 119 } 120 column.append(c); 121 i++; 122 } 123 i++; 124 // trailing list entry 125 if (column.length() > 0) { 126 String s = URLDecoder.decode(column.toString(), HConstants.UTF8_ENCODING); 127 this.columns.add(Bytes.toBytes(s)); 128 } 129 } catch (IndexOutOfBoundsException e) { 130 throw new IllegalArgumentException(e); 131 } catch (UnsupportedEncodingException e) { 132 // shouldn't happen 133 throw new RuntimeException(e); 134 } 135 return i; 136 } 137 138 private int parseTimestamp(final String path, int i) throws IllegalArgumentException { 139 if (i >= path.length()) { 140 return i; 141 } 142 long time0 = 0, time1 = 0; 143 try { 144 char c = 0; 145 StringBuilder stamp = new StringBuilder(); 146 while (i < path.length()) { 147 c = path.charAt(i); 148 if (c == '/' || c == ',') { 149 break; 150 } 151 stamp.append(c); 152 i++; 153 } 154 try { 155 time0 = Long.parseLong(URLDecoder.decode(stamp.toString(), HConstants.UTF8_ENCODING)); 156 } catch (NumberFormatException e) { 157 throw new IllegalArgumentException(e); 158 } 159 if (c == ',') { 160 stamp = new StringBuilder(); 161 i++; 162 while (i < path.length() && ((c = path.charAt(i)) != '/')) { 163 stamp.append(c); 164 i++; 165 } 166 try { 167 time1 = Long.parseLong(URLDecoder.decode(stamp.toString(), HConstants.UTF8_ENCODING)); 168 } catch (NumberFormatException e) { 169 throw new IllegalArgumentException(e); 170 } 171 } 172 if (c == '/') { 173 i++; 174 } 175 } catch (IndexOutOfBoundsException e) { 176 throw new IllegalArgumentException(e); 177 } catch (UnsupportedEncodingException e) { 178 // shouldn't happen 179 throw new RuntimeException(e); 180 } 181 if (time1 != 0) { 182 startTime = time0; 183 endTime = time1; 184 } else { 185 endTime = time0; 186 } 187 return i; 188 } 189 190 private int parseQueryParams(final String path, int i) { 191 if (i >= path.length()) { 192 return i; 193 } 194 StringBuilder query = new StringBuilder(); 195 try { 196 query.append(URLDecoder.decode(path.substring(i), HConstants.UTF8_ENCODING)); 197 } catch (UnsupportedEncodingException e) { 198 // should not happen 199 throw new RuntimeException(e); 200 } 201 i += query.length(); 202 int j = 0; 203 while (j < query.length()) { 204 char c = query.charAt(j); 205 if (c != '?' && c != '&') { 206 break; 207 } 208 if (++j > query.length()) { 209 throw new IllegalArgumentException("malformed query parameter"); 210 } 211 char what = query.charAt(j); 212 if (++j > query.length()) { 213 break; 214 } 215 c = query.charAt(j); 216 if (c != '=') { 217 throw new IllegalArgumentException("malformed query parameter"); 218 } 219 if (++j > query.length()) { 220 break; 221 } 222 switch (what) { 223 case 'm': { 224 StringBuilder sb = new StringBuilder(); 225 while (j <= query.length()) { 226 c = query.charAt(j); 227 if (c < '0' || c > '9') { 228 j--; 229 break; 230 } 231 sb.append(c); 232 } 233 maxVersions = Integer.parseInt(sb.toString()); 234 } 235 break; 236 case 'n': { 237 StringBuilder sb = new StringBuilder(); 238 while (j <= query.length()) { 239 c = query.charAt(j); 240 if (c < '0' || c > '9') { 241 j--; 242 break; 243 } 244 sb.append(c); 245 } 246 maxValues = Integer.parseInt(sb.toString()); 247 } 248 break; 249 default: 250 throw new IllegalArgumentException("unknown parameter '" + c + "'"); 251 } 252 } 253 return i; 254 } 255 256 public RowSpec(byte[] startRow, byte[] endRow, byte[][] columns, long startTime, long endTime, 257 int maxVersions) { 258 this.row = startRow; 259 this.endRow = endRow; 260 if (columns != null) { 261 Collections.addAll(this.columns, columns); 262 } 263 this.startTime = startTime; 264 this.endTime = endTime; 265 this.maxVersions = maxVersions; 266 } 267 268 public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns, long startTime, 269 long endTime, int maxVersions, Collection<String> labels) { 270 this(startRow, endRow, columns, startTime, endTime, maxVersions); 271 if (labels != null) { 272 this.labels.addAll(labels); 273 } 274 } 275 276 public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns, long startTime, 277 long endTime, int maxVersions) { 278 this.row = startRow; 279 this.endRow = endRow; 280 if (columns != null) { 281 this.columns.addAll(columns); 282 } 283 this.startTime = startTime; 284 this.endTime = endTime; 285 this.maxVersions = maxVersions; 286 } 287 288 public boolean isSingleRow() { 289 return endRow == null; 290 } 291 292 public int getMaxVersions() { 293 return maxVersions; 294 } 295 296 public void setMaxVersions(final int maxVersions) { 297 this.maxVersions = maxVersions; 298 } 299 300 public int getMaxValues() { 301 return maxValues; 302 } 303 304 public void setMaxValues(final int maxValues) { 305 this.maxValues = maxValues; 306 } 307 308 public boolean hasColumns() { 309 return !columns.isEmpty(); 310 } 311 312 public boolean hasLabels() { 313 return !labels.isEmpty(); 314 } 315 316 public byte[] getRow() { 317 return row; 318 } 319 320 public byte[] getStartRow() { 321 return row; 322 } 323 324 public boolean hasEndRow() { 325 return endRow != null; 326 } 327 328 public byte[] getEndRow() { 329 return endRow; 330 } 331 332 public void addColumn(final byte[] column) { 333 columns.add(column); 334 } 335 336 public byte[][] getColumns() { 337 return columns.toArray(new byte[columns.size()][]); 338 } 339 340 public List<String> getLabels() { 341 return labels; 342 } 343 344 public boolean hasTimestamp() { 345 return (startTime == 0) && (endTime != Long.MAX_VALUE); 346 } 347 348 public long getTimestamp() { 349 return endTime; 350 } 351 352 public long getStartTime() { 353 return startTime; 354 } 355 356 public void setStartTime(final long startTime) { 357 this.startTime = startTime; 358 } 359 360 public long getEndTime() { 361 return endTime; 362 } 363 364 public void setEndTime(long endTime) { 365 this.endTime = endTime; 366 } 367 368 @Override 369 public String toString() { 370 StringBuilder result = new StringBuilder(); 371 result.append("{startRow => '"); 372 if (row != null) { 373 result.append(Bytes.toString(row)); 374 } 375 result.append("', endRow => '"); 376 if (endRow != null) { 377 result.append(Bytes.toString(endRow)); 378 } 379 result.append("', columns => ["); 380 for (byte[] col : columns) { 381 result.append(" '"); 382 result.append(Bytes.toString(col)); 383 result.append("'"); 384 } 385 result.append(" ], startTime => "); 386 result.append(Long.toString(startTime)); 387 result.append(", endTime => "); 388 result.append(Long.toString(endTime)); 389 result.append(", maxVersions => "); 390 result.append(Integer.toString(maxVersions)); 391 result.append(", maxValues => "); 392 result.append(Integer.toString(maxValues)); 393 result.append("}"); 394 return result.toString(); 395 } 396}