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