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 static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertNotNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024 025import com.fasterxml.jackson.core.JsonFactory; 026import com.fasterxml.jackson.core.JsonParser; 027import com.fasterxml.jackson.core.JsonToken; 028import com.fasterxml.jackson.databind.ObjectMapper; 029import java.io.DataInputStream; 030import java.io.EOFException; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.Serializable; 034import java.net.URLEncoder; 035import java.nio.charset.StandardCharsets; 036import java.util.ArrayList; 037import java.util.Base64.Encoder; 038import java.util.Collections; 039import java.util.List; 040import javax.xml.bind.JAXBContext; 041import javax.xml.bind.JAXBException; 042import javax.xml.bind.Unmarshaller; 043import javax.xml.bind.annotation.XmlAccessType; 044import javax.xml.bind.annotation.XmlAccessorType; 045import javax.xml.bind.annotation.XmlElement; 046import javax.xml.bind.annotation.XmlRootElement; 047import javax.xml.parsers.SAXParserFactory; 048import org.apache.hadoop.conf.Configuration; 049import org.apache.hadoop.hbase.Cell; 050import org.apache.hadoop.hbase.HBaseTestingUtil; 051import org.apache.hadoop.hbase.TableName; 052import org.apache.hadoop.hbase.client.Admin; 053import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 054import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 055import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 056import org.apache.hadoop.hbase.filter.Filter; 057import org.apache.hadoop.hbase.filter.ParseFilter; 058import org.apache.hadoop.hbase.filter.PrefixFilter; 059import org.apache.hadoop.hbase.rest.client.Client; 060import org.apache.hadoop.hbase.rest.client.Cluster; 061import org.apache.hadoop.hbase.rest.client.Response; 062import org.apache.hadoop.hbase.rest.model.CellModel; 063import org.apache.hadoop.hbase.rest.model.CellSetModel; 064import org.apache.hadoop.hbase.rest.model.RowModel; 065import org.apache.hadoop.hbase.testclassification.MediumTests; 066import org.apache.hadoop.hbase.testclassification.RestTests; 067import org.apache.hadoop.hbase.util.Bytes; 068import org.junit.jupiter.api.AfterAll; 069import org.junit.jupiter.api.BeforeAll; 070import org.junit.jupiter.api.Tag; 071import org.junit.jupiter.api.Test; 072import org.xml.sax.InputSource; 073import org.xml.sax.XMLReader; 074 075import org.apache.hbase.thirdparty.com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; 076import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType; 077 078@Tag(RestTests.TAG) 079@Tag(MediumTests.TAG) 080public class TestTableScan { 081 082 private static final TableName TABLE = TableName.valueOf("TestScanResource"); 083 private static final String CFA = "a"; 084 private static final String CFB = "b"; 085 private static final String COLUMN_1 = CFA + ":1"; 086 private static final String COLUMN_2 = CFB + ":2"; 087 private static final String COLUMN_EMPTY = CFA + ":"; 088 private static Client client; 089 private static int expectedRows1; 090 private static int expectedRows2; 091 private static int expectedRows3; 092 private static Configuration conf; 093 094 private static final Encoder base64UrlEncoder = java.util.Base64.getUrlEncoder().withoutPadding(); 095 096 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 097 private static final HBaseRESTTestingUtility REST_TEST_UTIL = new HBaseRESTTestingUtility(); 098 099 @BeforeAll 100 public static void setUpBeforeClass() throws Exception { 101 conf = TEST_UTIL.getConfiguration(); 102 conf.set(Constants.CUSTOM_FILTERS, "CustomFilter:" + CustomFilter.class.getName()); 103 TEST_UTIL.startMiniCluster(); 104 REST_TEST_UTIL.startServletContainer(conf); 105 client = new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())); 106 Admin admin = TEST_UTIL.getAdmin(); 107 if (!admin.tableExists(TABLE)) { 108 TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TABLE); 109 ColumnFamilyDescriptor columnFamilyDescriptor = 110 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(CFA)).build(); 111 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 112 columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(CFB)).build(); 113 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 114 admin.createTable(tableDescriptorBuilder.build()); 115 expectedRows1 = TestScannerResource.insertData(conf, TABLE, COLUMN_1, 1.0); 116 expectedRows2 = TestScannerResource.insertData(conf, TABLE, COLUMN_2, 0.5); 117 expectedRows3 = TestScannerResource.insertData(conf, TABLE, COLUMN_EMPTY, 1.0); 118 } 119 } 120 121 @AfterAll 122 public static void tearDownAfterClass() throws Exception { 123 TEST_UTIL.getAdmin().disableTable(TABLE); 124 TEST_UTIL.getAdmin().deleteTable(TABLE); 125 REST_TEST_UTIL.shutdownServletContainer(); 126 TEST_UTIL.shutdownMiniCluster(); 127 } 128 129 @Test 130 public void testSimpleScannerXML() throws IOException, JAXBException { 131 // Test scanning particular columns 132 StringBuilder builder = new StringBuilder(); 133 builder.append("/*"); 134 builder.append("?"); 135 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 136 builder.append("&"); 137 builder.append(Constants.SCAN_LIMIT + "=10"); 138 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 139 assertEquals(200, response.getCode()); 140 assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type")); 141 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 142 Unmarshaller ush = ctx.createUnmarshaller(); 143 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 144 int count = TestScannerResource.countCellSet(model); 145 assertEquals(10, count); 146 checkRowsNotNull(model); 147 148 // Test with no limit. 149 builder = new StringBuilder(); 150 builder.append("/*"); 151 builder.append("?"); 152 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 153 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 154 assertEquals(200, response.getCode()); 155 assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type")); 156 model = (CellSetModel) ush.unmarshal(response.getStream()); 157 count = TestScannerResource.countCellSet(model); 158 assertEquals(expectedRows1, count); 159 checkRowsNotNull(model); 160 161 // Test with start and end row. 162 builder = new StringBuilder(); 163 builder.append("/*"); 164 builder.append("?"); 165 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 166 builder.append("&"); 167 builder.append(Constants.SCAN_START_ROW + "=aaa"); 168 builder.append("&"); 169 builder.append(Constants.SCAN_END_ROW + "=aay"); 170 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 171 assertEquals(200, response.getCode()); 172 model = (CellSetModel) ush.unmarshal(response.getStream()); 173 count = TestScannerResource.countCellSet(model); 174 RowModel startRow = model.getRows().get(0); 175 assertEquals("aaa", Bytes.toString(startRow.getKey())); 176 RowModel endRow = model.getRows().get(model.getRows().size() - 1); 177 assertEquals("aax", Bytes.toString(endRow.getKey())); 178 assertEquals(24, count); 179 checkRowsNotNull(model); 180 181 // Test with start row and limit. 182 builder = new StringBuilder(); 183 builder.append("/*"); 184 builder.append("?"); 185 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 186 builder.append("&"); 187 builder.append(Constants.SCAN_START_ROW + "=aaa"); 188 builder.append("&"); 189 builder.append(Constants.SCAN_LIMIT + "=15"); 190 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 191 assertEquals(200, response.getCode()); 192 assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type")); 193 model = (CellSetModel) ush.unmarshal(response.getStream()); 194 startRow = model.getRows().get(0); 195 assertEquals("aaa", Bytes.toString(startRow.getKey())); 196 count = TestScannerResource.countCellSet(model); 197 assertEquals(15, count); 198 checkRowsNotNull(model); 199 } 200 201 @Test 202 public void testSimpleScannerJson() throws IOException { 203 // Test scanning particular columns with limit. 204 StringBuilder builder = new StringBuilder(); 205 builder.append("/*"); 206 builder.append("?"); 207 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 208 builder.append("&"); 209 builder.append(Constants.SCAN_LIMIT + "=2"); 210 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_JSON); 211 assertEquals(200, response.getCode()); 212 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 213 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 214 MediaType.APPLICATION_JSON_TYPE); 215 CellSetModel model = mapper.readValue(response.getStream(), CellSetModel.class); 216 int count = TestScannerResource.countCellSet(model); 217 assertEquals(2, count); 218 checkRowsNotNull(model); 219 220 // Test scanning with no limit. 221 builder = new StringBuilder(); 222 builder.append("/*"); 223 builder.append("?"); 224 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_2); 225 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_JSON); 226 assertEquals(200, response.getCode()); 227 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 228 model = mapper.readValue(response.getStream(), CellSetModel.class); 229 count = TestScannerResource.countCellSet(model); 230 assertEquals(expectedRows2, count); 231 checkRowsNotNull(model); 232 233 // Test with start row and end row. 234 builder = new StringBuilder(); 235 builder.append("/*"); 236 builder.append("?"); 237 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 238 builder.append("&"); 239 builder.append(Constants.SCAN_START_ROW + "=aaa"); 240 builder.append("&"); 241 builder.append(Constants.SCAN_END_ROW + "=aay"); 242 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_JSON); 243 assertEquals(200, response.getCode()); 244 model = mapper.readValue(response.getStream(), CellSetModel.class); 245 RowModel startRow = model.getRows().get(0); 246 assertEquals("aaa", Bytes.toString(startRow.getKey())); 247 RowModel endRow = model.getRows().get(model.getRows().size() - 1); 248 assertEquals("aax", Bytes.toString(endRow.getKey())); 249 count = TestScannerResource.countCellSet(model); 250 assertEquals(24, count); 251 checkRowsNotNull(model); 252 } 253 254 /** 255 * An example to scan using listener in unmarshaller for XML. 256 * @throws Exception the exception 257 */ 258 @Test 259 public void testScanUsingListenerUnmarshallerXML() throws Exception { 260 StringBuilder builder = new StringBuilder(); 261 builder.append("/*"); 262 builder.append("?"); 263 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 264 builder.append("&"); 265 builder.append(Constants.SCAN_LIMIT + "=10"); 266 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 267 assertEquals(200, response.getCode()); 268 assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type")); 269 JAXBContext context = 270 JAXBContext.newInstance(ClientSideCellSetModel.class, RowModel.class, CellModel.class); 271 Unmarshaller unmarshaller = context.createUnmarshaller(); 272 273 final ClientSideCellSetModel.Listener listener = new ClientSideCellSetModel.Listener() { 274 @Override 275 public void handleRowModel(ClientSideCellSetModel helper, RowModel row) { 276 assertNotNull(row.getKey()); 277 assertFalse(row.getCells().isEmpty()); 278 } 279 }; 280 281 // install the callback on all ClientSideCellSetModel instances 282 unmarshaller.setListener(new Unmarshaller.Listener() { 283 @Override 284 public void beforeUnmarshal(Object target, Object parent) { 285 if (target instanceof ClientSideCellSetModel) { 286 ((ClientSideCellSetModel) target).setCellSetModelListener(listener); 287 } 288 } 289 290 @Override 291 public void afterUnmarshal(Object target, Object parent) { 292 if (target instanceof ClientSideCellSetModel) { 293 ((ClientSideCellSetModel) target).setCellSetModelListener(null); 294 } 295 } 296 }); 297 298 // create a new XML parser 299 SAXParserFactory factory = SAXParserFactory.newInstance(); 300 factory.setNamespaceAware(true); 301 XMLReader reader = factory.newSAXParser().getXMLReader(); 302 reader.setContentHandler(unmarshaller.getUnmarshallerHandler()); 303 assertFalse(ClientSideCellSetModel.listenerInvoked); 304 reader.parse(new InputSource(response.getStream())); 305 assertTrue(ClientSideCellSetModel.listenerInvoked); 306 307 } 308 309 @Test 310 public void testStreamingJSON() throws Exception { 311 // Test with start row and end row. 312 StringBuilder builder = new StringBuilder(); 313 builder.append("/*"); 314 builder.append("?"); 315 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 316 builder.append("&"); 317 builder.append(Constants.SCAN_START_ROW + "=aaa"); 318 builder.append("&"); 319 builder.append(Constants.SCAN_END_ROW + "=aay"); 320 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_JSON); 321 assertEquals(200, response.getCode()); 322 323 int count = 0; 324 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 325 MediaType.APPLICATION_JSON_TYPE); 326 JsonFactory jfactory = new JsonFactory(mapper); 327 JsonParser jParser = jfactory.createJsonParser(response.getStream()); 328 boolean found = false; 329 while (jParser.nextToken() != JsonToken.END_OBJECT) { 330 if (jParser.getCurrentToken() == JsonToken.START_OBJECT && found) { 331 RowModel row = jParser.readValueAs(RowModel.class); 332 assertNotNull(row.getKey()); 333 for (int i = 0; i < row.getCells().size(); i++) { 334 if (count == 0) { 335 assertEquals("aaa", Bytes.toString(row.getKey())); 336 } 337 if (count == 23) { 338 assertEquals("aax", Bytes.toString(row.getKey())); 339 } 340 count++; 341 } 342 jParser.skipChildren(); 343 } else { 344 found = jParser.getCurrentToken() == JsonToken.START_ARRAY; 345 } 346 } 347 assertEquals(24, count); 348 } 349 350 @Test 351 public void testSimpleScannerProtobuf() throws Exception { 352 StringBuilder builder = new StringBuilder(); 353 builder.append("/*"); 354 builder.append("?"); 355 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 356 builder.append("&"); 357 builder.append(Constants.SCAN_LIMIT + "=15"); 358 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_PROTOBUF); 359 assertEquals(200, response.getCode()); 360 assertEquals(Constants.MIMETYPE_PROTOBUF, response.getHeader("content-type")); 361 int rowCount = readProtobufStream(response.getStream()); 362 assertEquals(15, rowCount); 363 364 // Test with start row and end row. 365 builder = new StringBuilder(); 366 builder.append("/*"); 367 builder.append("?"); 368 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 369 builder.append("&"); 370 builder.append(Constants.SCAN_START_ROW + "=aaa"); 371 builder.append("&"); 372 builder.append(Constants.SCAN_END_ROW + "=aay"); 373 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_PROTOBUF); 374 assertEquals(200, response.getCode()); 375 assertEquals(Constants.MIMETYPE_PROTOBUF, response.getHeader("content-type")); 376 rowCount = readProtobufStream(response.getStream()); 377 assertEquals(24, rowCount); 378 } 379 380 private void checkRowsNotNull(CellSetModel model) { 381 for (RowModel row : model.getRows()) { 382 assertNotNull(row.getKey()); 383 assertFalse(row.getCells().isEmpty()); 384 } 385 } 386 387 /** 388 * Read protobuf stream. 389 * @param inputStream the input stream 390 * @return The number of rows in the cell set model. 391 * @throws IOException Signals that an I/O exception has occurred. 392 */ 393 public int readProtobufStream(InputStream inputStream) throws IOException { 394 DataInputStream stream = new DataInputStream(inputStream); 395 CellSetModel model = null; 396 int rowCount = 0; 397 try { 398 while (true) { 399 byte[] lengthBytes = new byte[2]; 400 int readBytes = stream.read(lengthBytes); 401 if (readBytes == -1) { 402 break; 403 } 404 assertEquals(2, readBytes); 405 int length = Bytes.toShort(lengthBytes); 406 byte[] cellset = new byte[length]; 407 stream.read(cellset); 408 model = new CellSetModel(); 409 model.getObjectFromMessage(cellset); 410 checkRowsNotNull(model); 411 rowCount = rowCount + TestScannerResource.countCellSet(model); 412 } 413 } catch (EOFException exp) { 414 exp.printStackTrace(); 415 } finally { 416 stream.close(); 417 } 418 return rowCount; 419 } 420 421 @Test 422 public void testScanningUnknownColumnJson() throws IOException { 423 // Test scanning particular columns with limit. 424 StringBuilder builder = new StringBuilder(); 425 builder.append("/*"); 426 builder.append("?"); 427 builder.append(Constants.SCAN_COLUMN + "=a:test"); 428 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_JSON); 429 assertEquals(200, response.getCode()); 430 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 431 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 432 MediaType.APPLICATION_JSON_TYPE); 433 CellSetModel model = mapper.readValue(response.getStream(), CellSetModel.class); 434 int count = TestScannerResource.countCellSet(model); 435 assertEquals(0, count); 436 } 437 438 @Test 439 public void testSimpleFilter() throws IOException, JAXBException { 440 StringBuilder builder = new StringBuilder(); 441 builder.append("/*"); 442 builder.append("?"); 443 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 444 builder.append("&"); 445 builder.append(Constants.SCAN_START_ROW + "=aaa"); 446 builder.append("&"); 447 builder.append(Constants.SCAN_END_ROW + "=aay"); 448 builder.append("&"); 449 builder.append(Constants.FILTER + "=" + URLEncoder.encode("PrefixFilter('aab')", "UTF-8")); 450 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 451 assertEquals(200, response.getCode()); 452 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 453 Unmarshaller ush = ctx.createUnmarshaller(); 454 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 455 int count = TestScannerResource.countCellSet(model); 456 assertEquals(1, count); 457 assertEquals("aab", 458 new String(model.getRows().get(0).getCells().get(0).getValue(), StandardCharsets.UTF_8)); 459 } 460 461 // This only tests the Base64Url encoded filter definition. 462 // base64 encoded row values are not implemented for this endpoint 463 @Test 464 public void testSimpleFilterBase64() throws IOException, JAXBException { 465 StringBuilder builder = new StringBuilder(); 466 builder.append("/*"); 467 builder.append("?"); 468 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 469 builder.append("&"); 470 builder.append(Constants.SCAN_START_ROW + "=aaa"); 471 builder.append("&"); 472 builder.append(Constants.SCAN_END_ROW + "=aay"); 473 builder.append("&"); 474 builder.append(Constants.FILTER_B64 + "=" + base64UrlEncoder 475 .encodeToString("PrefixFilter('aab')".getBytes(StandardCharsets.UTF_8.toString()))); 476 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 477 assertEquals(200, response.getCode()); 478 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 479 Unmarshaller ush = ctx.createUnmarshaller(); 480 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 481 int count = TestScannerResource.countCellSet(model); 482 assertEquals(1, count); 483 assertEquals("aab", 484 new String(model.getRows().get(0).getCells().get(0).getValue(), StandardCharsets.UTF_8)); 485 } 486 487 @Test 488 public void testQualifierAndPrefixFilters() throws IOException, JAXBException { 489 StringBuilder builder = new StringBuilder(); 490 builder.append("/abc*"); 491 builder.append("?"); 492 builder 493 .append(Constants.FILTER + "=" + URLEncoder.encode("QualifierFilter(=,'binary:1')", "UTF-8")); 494 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 495 assertEquals(200, response.getCode()); 496 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 497 Unmarshaller ush = ctx.createUnmarshaller(); 498 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 499 int count = TestScannerResource.countCellSet(model); 500 assertEquals(1, count); 501 assertEquals("abc", 502 new String(model.getRows().get(0).getCells().get(0).getValue(), StandardCharsets.UTF_8)); 503 } 504 505 @Test 506 public void testCompoundFilter() throws IOException, JAXBException { 507 StringBuilder builder = new StringBuilder(); 508 builder.append("/*"); 509 builder.append("?"); 510 builder.append(Constants.FILTER + "=" 511 + URLEncoder.encode("PrefixFilter('abc') AND QualifierFilter(=,'binary:1')", "UTF-8")); 512 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 513 assertEquals(200, response.getCode()); 514 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 515 Unmarshaller ush = ctx.createUnmarshaller(); 516 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 517 int count = TestScannerResource.countCellSet(model); 518 assertEquals(1, count); 519 assertEquals("abc", 520 new String(model.getRows().get(0).getCells().get(0).getValue(), StandardCharsets.UTF_8)); 521 } 522 523 @Test 524 public void testCustomFilter() throws IOException, JAXBException { 525 StringBuilder builder = new StringBuilder(); 526 builder.append("/a*"); 527 builder.append("?"); 528 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 529 builder.append("&"); 530 builder.append(Constants.FILTER + "=" + URLEncoder.encode("CustomFilter('abc')", "UTF-8")); 531 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 532 assertEquals(200, response.getCode()); 533 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 534 Unmarshaller ush = ctx.createUnmarshaller(); 535 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 536 int count = TestScannerResource.countCellSet(model); 537 assertEquals(1, count); 538 assertEquals("abc", 539 new String(model.getRows().get(0).getCells().get(0).getValue(), StandardCharsets.UTF_8)); 540 } 541 542 @Test 543 public void testNegativeCustomFilter() throws IOException, JAXBException { 544 StringBuilder builder = new StringBuilder(); 545 builder.append("/b*"); 546 builder.append("?"); 547 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 548 builder.append("&"); 549 builder.append(Constants.FILTER + "=" + URLEncoder.encode("CustomFilter('abc')", "UTF-8")); 550 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 551 assertEquals(200, response.getCode()); 552 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 553 Unmarshaller ush = ctx.createUnmarshaller(); 554 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 555 int count = TestScannerResource.countCellSet(model); 556 // Should return no rows as the filters conflict 557 assertEquals(0, count); 558 } 559 560 @Test 561 public void testReversed() throws IOException, JAXBException { 562 StringBuilder builder = new StringBuilder(); 563 builder.append("/*"); 564 builder.append("?"); 565 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 566 builder.append("&"); 567 builder.append(Constants.SCAN_START_ROW + "=aaa"); 568 builder.append("&"); 569 builder.append(Constants.SCAN_END_ROW + "=aay"); 570 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 571 assertEquals(200, response.getCode()); 572 JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class); 573 Unmarshaller ush = ctx.createUnmarshaller(); 574 CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream()); 575 int count = TestScannerResource.countCellSet(model); 576 assertEquals(24, count); 577 List<RowModel> rowModels = model.getRows().subList(1, count); 578 579 // reversed 580 builder = new StringBuilder(); 581 builder.append("/*"); 582 builder.append("?"); 583 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 584 builder.append("&"); 585 builder.append(Constants.SCAN_START_ROW + "=aay"); 586 builder.append("&"); 587 builder.append(Constants.SCAN_END_ROW + "=aaa"); 588 builder.append("&"); 589 builder.append(Constants.SCAN_REVERSED + "=true"); 590 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML); 591 assertEquals(200, response.getCode()); 592 model = (CellSetModel) ush.unmarshal(response.getStream()); 593 count = TestScannerResource.countCellSet(model); 594 assertEquals(24, count); 595 List<RowModel> reversedRowModels = model.getRows().subList(1, count); 596 597 Collections.reverse(reversedRowModels); 598 assertEquals(rowModels.size(), reversedRowModels.size()); 599 for (int i = 0; i < rowModels.size(); i++) { 600 RowModel rowModel = rowModels.get(i); 601 RowModel reversedRowModel = reversedRowModels.get(i); 602 603 assertEquals(new String(rowModel.getKey(), StandardCharsets.UTF_8), 604 new String(reversedRowModel.getKey(), StandardCharsets.UTF_8)); 605 assertEquals(new String(rowModel.getCells().get(0).getValue(), StandardCharsets.UTF_8), 606 new String(reversedRowModel.getCells().get(0).getValue(), StandardCharsets.UTF_8)); 607 } 608 } 609 610 @Test 611 public void testColumnWithEmptyQualifier() throws IOException { 612 // Test scanning with empty qualifier 613 StringBuilder builder = new StringBuilder(); 614 builder.append("/*"); 615 builder.append("?"); 616 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_EMPTY); 617 Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_JSON); 618 assertEquals(200, response.getCode()); 619 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 620 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 621 MediaType.APPLICATION_JSON_TYPE); 622 CellSetModel model = mapper.readValue(response.getStream(), CellSetModel.class); 623 int count = TestScannerResource.countCellSet(model); 624 assertEquals(expectedRows3, count); 625 checkRowsNotNull(model); 626 RowModel startRow = model.getRows().get(0); 627 assertEquals("aaa", Bytes.toString(startRow.getKey())); 628 assertEquals(1, startRow.getCells().size()); 629 630 // Test scanning with empty qualifier and normal qualifier 631 builder = new StringBuilder(); 632 builder.append("/*"); 633 builder.append("?"); 634 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1); 635 builder.append("&"); 636 builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_EMPTY); 637 response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_JSON); 638 assertEquals(200, response.getCode()); 639 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 640 mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 641 MediaType.APPLICATION_JSON_TYPE); 642 model = mapper.readValue(response.getStream(), CellSetModel.class); 643 count = TestScannerResource.countCellSet(model); 644 assertEquals(expectedRows1 + expectedRows3, count); 645 checkRowsNotNull(model); 646 } 647 648 public static class CustomFilter extends PrefixFilter { 649 private byte[] key = null; 650 651 public CustomFilter(byte[] key) { 652 super(key); 653 } 654 655 @Override 656 public boolean filterRowKey(Cell cell) { 657 int cmp = Bytes.compareTo(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength(), 658 this.key, 0, this.key.length); 659 return cmp != 0; 660 } 661 662 public static Filter createFilterFromArguments(ArrayList<byte[]> filterArguments) { 663 byte[] prefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0)); 664 return new CustomFilter(prefix); 665 } 666 } 667 668 /** 669 * The Class ClientSideCellSetModel which mimics cell set model, and contains listener to perform 670 * user defined operations on the row model. 671 */ 672 @XmlRootElement(name = "CellSet") 673 @XmlAccessorType(XmlAccessType.FIELD) 674 public static class ClientSideCellSetModel implements Serializable { 675 private static final long serialVersionUID = 1L; 676 677 /** 678 * This list is not a real list; instead it will notify a listener whenever JAXB has 679 * unmarshalled the next row. 680 */ 681 @XmlElement(name = "Row") 682 private List<RowModel> row; 683 684 static boolean listenerInvoked = false; 685 686 /** 687 * Install a listener for row model on this object. If l is null, the listener is removed again. 688 */ 689 public void setCellSetModelListener(final Listener l) { 690 row = (l == null) ? null : new ArrayList<RowModel>() { 691 private static final long serialVersionUID = 1L; 692 693 @Override 694 public boolean add(RowModel o) { 695 l.handleRowModel(ClientSideCellSetModel.this, o); 696 listenerInvoked = true; 697 return false; 698 } 699 }; 700 } 701 702 /** 703 * This listener is invoked every time a new row model is unmarshalled. 704 */ 705 public interface Listener { 706 void handleRowModel(ClientSideCellSetModel helper, RowModel rowModel); 707 } 708 } 709}