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