001/* 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020package org.apache.hadoop.hbase.rest; 021 022import java.io.UnsupportedEncodingException; 023import java.net.URLDecoder; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.List; 028import java.util.TreeSet; 029 030import org.apache.yetus.audience.InterfaceAudience; 031import org.apache.hadoop.hbase.HConstants; 032import org.apache.hadoop.hbase.util.Bytes; 033 034/** 035 * Parses a path based row/column/timestamp specification into its component 036 * elements. 037 * <p> 038 * 039 */ 040@InterfaceAudience.Private 041public class RowSpec { 042 public static final long DEFAULT_START_TIMESTAMP = 0; 043 public static final long DEFAULT_END_TIMESTAMP = Long.MAX_VALUE; 044 045 private byte[] row = HConstants.EMPTY_START_ROW; 046 private byte[] endRow = null; 047 private TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR); 048 private List<String> labels = new ArrayList<>(); 049 private long startTime = DEFAULT_START_TIMESTAMP; 050 private long endTime = DEFAULT_END_TIMESTAMP; 051 private int maxVersions = 1; 052 private int maxValues = Integer.MAX_VALUE; 053 054 public RowSpec(String path) throws IllegalArgumentException { 055 int i = 0; 056 while (path.charAt(i) == '/') { 057 i++; 058 } 059 i = parseRowKeys(path, i); 060 i = parseColumns(path, i); 061 i = parseTimestamp(path, i); 062 i = parseQueryParams(path, i); 063 } 064 065 private int parseRowKeys(final String path, int i) 066 throws IllegalArgumentException { 067 String startRow = null, endRow = null; 068 try { 069 StringBuilder sb = new StringBuilder(); 070 char c; 071 while (i < path.length() && (c = path.charAt(i)) != '/') { 072 sb.append(c); 073 i++; 074 } 075 i++; 076 String row = startRow = sb.toString(); 077 int idx = startRow.indexOf(','); 078 if (idx != -1) { 079 startRow = URLDecoder.decode(row.substring(0, idx), 080 HConstants.UTF8_ENCODING); 081 endRow = URLDecoder.decode(row.substring(idx + 1), 082 HConstants.UTF8_ENCODING); 083 } else { 084 startRow = URLDecoder.decode(row, HConstants.UTF8_ENCODING); 085 } 086 } catch (IndexOutOfBoundsException e) { 087 throw new IllegalArgumentException(e); 088 } catch (UnsupportedEncodingException e) { 089 throw new RuntimeException(e); 090 } 091 // HBase does not support wildcards on row keys so we will emulate a 092 // suffix glob by synthesizing appropriate start and end row keys for 093 // table scanning 094 if (startRow.charAt(startRow.length() - 1) == '*') { 095 if (endRow != null) 096 throw new IllegalArgumentException("invalid path: start row "+ 097 "specified with wildcard"); 098 this.row = Bytes.toBytes(startRow.substring(0, 099 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.parseLong(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.parseLong(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.parseInt(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.parseInt(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 Collections.addAll(this.columns, columns); 274 } 275 this.startTime = startTime; 276 this.endTime = endTime; 277 this.maxVersions = maxVersions; 278 } 279 280 public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns, 281 long startTime, long endTime, int maxVersions, Collection<String> labels) { 282 this(startRow, endRow, columns, startTime, endTime, maxVersions); 283 if(labels != null) { 284 this.labels.addAll(labels); 285 } 286 } 287 public RowSpec(byte[] startRow, byte[] endRow, Collection<byte[]> columns, 288 long startTime, long endTime, int maxVersions) { 289 this.row = startRow; 290 this.endRow = endRow; 291 if (columns != null) { 292 this.columns.addAll(columns); 293 } 294 this.startTime = startTime; 295 this.endTime = endTime; 296 this.maxVersions = maxVersions; 297 } 298 299 public boolean isSingleRow() { 300 return endRow == null; 301 } 302 303 public int getMaxVersions() { 304 return maxVersions; 305 } 306 307 public void setMaxVersions(final int maxVersions) { 308 this.maxVersions = maxVersions; 309 } 310 311 public int getMaxValues() { 312 return maxValues; 313 } 314 315 public void setMaxValues(final int maxValues) { 316 this.maxValues = maxValues; 317 } 318 319 public boolean hasColumns() { 320 return !columns.isEmpty(); 321 } 322 323 public boolean hasLabels() { 324 return !labels.isEmpty(); 325 } 326 327 public byte[] getRow() { 328 return row; 329 } 330 331 public byte[] getStartRow() { 332 return row; 333 } 334 335 public boolean hasEndRow() { 336 return endRow != null; 337 } 338 339 public byte[] getEndRow() { 340 return endRow; 341 } 342 343 public void addColumn(final byte[] column) { 344 columns.add(column); 345 } 346 347 public byte[][] getColumns() { 348 return columns.toArray(new byte[columns.size()][]); 349 } 350 351 public List<String> getLabels() { 352 return labels; 353 } 354 355 public boolean hasTimestamp() { 356 return (startTime == 0) && (endTime != Long.MAX_VALUE); 357 } 358 359 public long getTimestamp() { 360 return endTime; 361 } 362 363 public long getStartTime() { 364 return startTime; 365 } 366 367 public void setStartTime(final long startTime) { 368 this.startTime = startTime; 369 } 370 371 public long getEndTime() { 372 return endTime; 373 } 374 375 public void setEndTime(long endTime) { 376 this.endTime = endTime; 377 } 378 379 @Override 380 public String toString() { 381 StringBuilder result = new StringBuilder(); 382 result.append("{startRow => '"); 383 if (row != null) { 384 result.append(Bytes.toString(row)); 385 } 386 result.append("', endRow => '"); 387 if (endRow != null) { 388 result.append(Bytes.toString(endRow)); 389 } 390 result.append("', columns => ["); 391 for (byte[] col: columns) { 392 result.append(" '"); 393 result.append(Bytes.toString(col)); 394 result.append("'"); 395 } 396 result.append(" ], startTime => "); 397 result.append(Long.toString(startTime)); 398 result.append(", endTime => "); 399 result.append(Long.toString(endTime)); 400 result.append(", maxVersions => "); 401 result.append(Integer.toString(maxVersions)); 402 result.append(", maxValues => "); 403 result.append(Integer.toString(maxValues)); 404 result.append("}"); 405 return result.toString(); 406 } 407}