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