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