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