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