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