View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.rest;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertNotNull;
23  import static org.junit.Assert.assertTrue;
24  
25  import com.fasterxml.jackson.core.JsonFactory;
26  import com.fasterxml.jackson.core.JsonParser;
27  import com.fasterxml.jackson.core.JsonToken;
28  import com.fasterxml.jackson.databind.ObjectMapper;
29  
30  import java.io.DataInputStream;
31  import java.io.EOFException;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.Serializable;
35  import java.net.URLEncoder;
36  import java.nio.charset.StandardCharsets;
37  import java.util.ArrayList;
38  import java.util.Collections;
39  import java.util.List;
40  
41  import javax.ws.rs.core.MediaType;
42  import javax.xml.bind.JAXBContext;
43  import javax.xml.bind.JAXBException;
44  import javax.xml.bind.Unmarshaller;
45  import javax.xml.bind.annotation.XmlAccessType;
46  import javax.xml.bind.annotation.XmlAccessorType;
47  import javax.xml.bind.annotation.XmlElement;
48  import javax.xml.bind.annotation.XmlRootElement;
49  import javax.xml.parsers.SAXParserFactory;
50  
51  import org.apache.hadoop.conf.Configuration;
52  import org.apache.hadoop.hbase.HBaseTestingUtility;
53  import org.apache.hadoop.hbase.HColumnDescriptor;
54  import org.apache.hadoop.hbase.HTableDescriptor;
55  import org.apache.hadoop.hbase.TableName;
56  import org.apache.hadoop.hbase.client.Admin;
57  import org.apache.hadoop.hbase.filter.Filter;
58  import org.apache.hadoop.hbase.filter.ParseFilter;
59  import org.apache.hadoop.hbase.filter.PrefixFilter;
60  import org.apache.hadoop.hbase.rest.client.Client;
61  import org.apache.hadoop.hbase.rest.client.Cluster;
62  import org.apache.hadoop.hbase.rest.client.Response;
63  import org.apache.hadoop.hbase.rest.model.CellModel;
64  import org.apache.hadoop.hbase.rest.model.CellSetModel;
65  import org.apache.hadoop.hbase.rest.model.RowModel;
66  import org.apache.hadoop.hbase.rest.provider.JacksonProvider;
67  import org.apache.hadoop.hbase.testclassification.MediumTests;
68  import org.apache.hadoop.hbase.util.Bytes;
69  import org.junit.AfterClass;
70  import org.junit.BeforeClass;
71  import org.junit.Test;
72  import org.junit.experimental.categories.Category;
73  import org.xml.sax.InputSource;
74  import org.xml.sax.XMLReader;
75  
76  @Category(MediumTests.class)
77  public class TestTableScan {
78    private static final TableName TABLE = TableName.valueOf("TestScanResource");
79    private static final String CFA = "a";
80    private static final String CFB = "b";
81    private static final String COLUMN_1 = CFA + ":1";
82    private static final String COLUMN_2 = CFB + ":2";
83    private static final String COLUMN_EMPTY = CFA + ":";
84    private static Client client;
85    private static int expectedRows1;
86    private static int expectedRows2;
87    private static int expectedRows3;
88    private static Configuration conf;
89  
90    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
91    private static final HBaseRESTTestingUtility REST_TEST_UTIL =
92      new HBaseRESTTestingUtility();
93  
94    @BeforeClass
95    public static void setUpBeforeClass() throws Exception {
96      conf = TEST_UTIL.getConfiguration();
97      conf.set(Constants.CUSTOM_FILTERS, "CustomFilter:" + CustomFilter.class.getName());
98      TEST_UTIL.startMiniCluster();
99      REST_TEST_UTIL.startServletContainer(conf);
100     client = new Client(new Cluster().add("localhost",
101       REST_TEST_UTIL.getServletPort()));
102     Admin admin = TEST_UTIL.getHBaseAdmin();
103     if (!admin.tableExists(TABLE)) {
104       HTableDescriptor htd = new HTableDescriptor(TABLE);
105       htd.addFamily(new HColumnDescriptor(CFA));
106       htd.addFamily(new HColumnDescriptor(CFB));
107       admin.createTable(htd);
108       expectedRows1 = TestScannerResource.insertData(conf, TABLE, COLUMN_1, 1.0);
109       expectedRows2 = TestScannerResource.insertData(conf, TABLE, COLUMN_2, 0.5);
110       expectedRows3 = TestScannerResource.insertData(conf, TABLE, COLUMN_EMPTY, 1.0);
111     }
112   }
113 
114   @AfterClass
115   public static void tearDownAfterClass() throws Exception {
116     TEST_UTIL.getHBaseAdmin().disableTable(TABLE);
117     TEST_UTIL.getHBaseAdmin().deleteTable(TABLE);
118     REST_TEST_UTIL.shutdownServletContainer();
119     TEST_UTIL.shutdownMiniCluster();
120   }
121 
122   @Test
123   public void testSimpleScannerXML() throws IOException, JAXBException {
124     // Test scanning particular columns
125     StringBuilder builder = new StringBuilder();
126     builder.append("/*");
127     builder.append("?");
128     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
129     builder.append("&");
130     builder.append(Constants.SCAN_LIMIT + "=10");
131     Response response = client.get("/" + TABLE + builder.toString(),
132       Constants.MIMETYPE_XML);
133     assertEquals(200, response.getCode());
134     assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type"));
135     JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class);
136     Unmarshaller ush = ctx.createUnmarshaller();
137     CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream());
138     int count = TestScannerResource.countCellSet(model);
139     assertEquals(10, count);
140     checkRowsNotNull(model);
141 
142     //Test with no limit.
143     builder = new StringBuilder();
144     builder.append("/*");
145     builder.append("?");
146     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
147     response = client.get("/" + TABLE + builder.toString(),
148       Constants.MIMETYPE_XML);
149     assertEquals(200, response.getCode());
150     assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type"));
151     model = (CellSetModel) ush.unmarshal(response.getStream());
152     count = TestScannerResource.countCellSet(model);
153     assertEquals(expectedRows1, count);
154     checkRowsNotNull(model);
155 
156     //Test with start and end row.
157     builder = new StringBuilder();
158     builder.append("/*");
159     builder.append("?");
160     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
161     builder.append("&");
162     builder.append(Constants.SCAN_START_ROW + "=aaa");
163     builder.append("&");
164     builder.append(Constants.SCAN_END_ROW + "=aay");
165     response = client.get("/" + TABLE + builder.toString(),
166       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(),
187       Constants.MIMETYPE_XML);
188     assertEquals(200, response.getCode());
189     assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type"));
190     model = (CellSetModel) ush.unmarshal(response.getStream());
191     startRow = model.getRows().get(0);
192     assertEquals("aaa", Bytes.toString(startRow.getKey()));
193     count = TestScannerResource.countCellSet(model);
194     assertEquals(15, count);
195     checkRowsNotNull(model);
196   }
197 
198   @Test
199   public void testSimpleScannerJson() throws IOException {
200     // Test scanning particular columns with limit.
201     StringBuilder builder = new StringBuilder();
202     builder.append("/*");
203     builder.append("?");
204     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
205     builder.append("&");
206     builder.append(Constants.SCAN_LIMIT + "=20");
207     Response response = client.get("/" + TABLE + builder.toString(),
208       Constants.MIMETYPE_JSON);
209     assertEquals(200, response.getCode());
210     assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
211     ObjectMapper mapper = new JacksonProvider()
212         .locateMapper(CellSetModel.class, MediaType.APPLICATION_JSON_TYPE);
213     CellSetModel model = mapper.readValue(response.getStream(), CellSetModel.class);
214     int count = TestScannerResource.countCellSet(model);
215     assertEquals(20, count);
216     checkRowsNotNull(model);
217 
218     //Test scanning with no limit.
219     builder = new StringBuilder();
220     builder.append("/*");
221     builder.append("?");
222     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_2);
223     response = client.get("/" + TABLE + builder.toString(),
224       Constants.MIMETYPE_JSON);
225     assertEquals(200, response.getCode());
226     assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
227     model = mapper.readValue(response.getStream(), CellSetModel.class);
228     count = TestScannerResource.countCellSet(model);
229     assertEquals(expectedRows2, count);
230     checkRowsNotNull(model);
231 
232     //Test with start row and end row.
233     builder = new StringBuilder();
234     builder.append("/*");
235     builder.append("?");
236     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
237     builder.append("&");
238     builder.append(Constants.SCAN_START_ROW + "=aaa");
239     builder.append("&");
240     builder.append(Constants.SCAN_END_ROW + "=aay");
241     response = client.get("/" + TABLE + builder.toString(),
242       Constants.MIMETYPE_JSON);
243     assertEquals(200, response.getCode());
244     model = mapper.readValue(response.getStream(), CellSetModel.class);
245     RowModel startRow = model.getRows().get(0);
246     assertEquals("aaa", Bytes.toString(startRow.getKey()));
247     RowModel endRow = model.getRows().get(model.getRows().size() - 1);
248     assertEquals("aax", Bytes.toString(endRow.getKey()));
249     count = TestScannerResource.countCellSet(model);
250     assertEquals(24, count);
251     checkRowsNotNull(model);
252   }
253 
254   /**
255    * An example to scan using listener in unmarshaller for XML.
256    * @throws Exception the exception
257    */
258   @Test
259   public void testScanUsingListenerUnmarshallerXML() throws Exception {
260     StringBuilder builder = new StringBuilder();
261     builder.append("/*");
262     builder.append("?");
263     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
264     builder.append("&");
265     builder.append(Constants.SCAN_LIMIT + "=10");
266     Response response = client.get("/" + TABLE + builder.toString(),
267       Constants.MIMETYPE_XML);
268     assertEquals(200, response.getCode());
269     assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type"));
270     JAXBContext context = JAXBContext.newInstance(ClientSideCellSetModel.class, RowModel.class,
271       CellModel.class);
272     Unmarshaller unmarshaller = context.createUnmarshaller();
273 
274     final ClientSideCellSetModel.Listener listener = new ClientSideCellSetModel.Listener() {
275       @Override
276       public void handleRowModel(ClientSideCellSetModel helper, RowModel row) {
277         assertTrue(row.getKey() != null);
278         assertTrue(row.getCells().size() > 0);
279       }
280     };
281 
282     // install the callback on all ClientSideCellSetModel instances
283     unmarshaller.setListener(new Unmarshaller.Listener() {
284         @Override
285         public void beforeUnmarshal(Object target, Object parent) {
286           if (target instanceof ClientSideCellSetModel) {
287             ((ClientSideCellSetModel) target).setCellSetModelListener(listener);
288           }
289         }
290 
291         @Override
292         public void afterUnmarshal(Object target, Object parent) {
293           if (target instanceof ClientSideCellSetModel) {
294             ((ClientSideCellSetModel) target).setCellSetModelListener(null);
295           }
296         }
297     });
298 
299     // create a new XML parser
300     SAXParserFactory factory = SAXParserFactory.newInstance();
301     factory.setNamespaceAware(true);
302     XMLReader reader = factory.newSAXParser().getXMLReader();
303     reader.setContentHandler(unmarshaller.getUnmarshallerHandler());
304     assertFalse(ClientSideCellSetModel.listenerInvoked);
305     reader.parse(new InputSource(response.getStream()));
306     assertTrue(ClientSideCellSetModel.listenerInvoked);
307 
308   }
309 
310   @Test
311   public void testStreamingJSON() throws Exception {
312     // Test scanning particular columns with limit.
313     StringBuilder builder = new StringBuilder();
314     builder.append("/*");
315     builder.append("?");
316     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
317     builder.append("&");
318     builder.append(Constants.SCAN_LIMIT + "=20");
319     Response response = client.get("/" + TABLE + builder.toString(),
320       Constants.MIMETYPE_JSON);
321     assertEquals(200, response.getCode());
322     assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
323     ObjectMapper mapper = new JacksonProvider()
324         .locateMapper(CellSetModel.class, MediaType.APPLICATION_JSON_TYPE);
325     CellSetModel model = mapper.readValue(response.getStream(), CellSetModel.class);
326     int count = TestScannerResource.countCellSet(model);
327     assertEquals(20, count);
328     checkRowsNotNull(model);
329 
330     //Test scanning with no limit.
331     builder = new StringBuilder();
332     builder.append("/*");
333     builder.append("?");
334     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_2);
335     response = client.get("/" + TABLE + builder.toString(),
336       Constants.MIMETYPE_JSON);
337     assertEquals(200, response.getCode());
338     assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
339     model = mapper.readValue(response.getStream(), CellSetModel.class);
340     count = TestScannerResource.countCellSet(model);
341     assertEquals(expectedRows2, count);
342     checkRowsNotNull(model);
343 
344     //Test with start row and end row.
345     builder = new StringBuilder();
346     builder.append("/*");
347     builder.append("?");
348     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
349     builder.append("&");
350     builder.append(Constants.SCAN_START_ROW + "=aaa");
351     builder.append("&");
352     builder.append(Constants.SCAN_END_ROW + "=aay");
353     response = client.get("/" + TABLE + builder.toString(),
354       Constants.MIMETYPE_JSON);
355     assertEquals(200, response.getCode());
356 
357     count = 0;
358     JsonFactory jfactory = new JsonFactory(mapper);
359     JsonParser jParser = jfactory.createParser(response.getStream());
360     boolean found = false;
361     while (jParser.nextToken() != JsonToken.END_OBJECT) {
362       if(jParser.getCurrentToken() == JsonToken.START_OBJECT && found) {
363         RowModel row = jParser.readValueAs(RowModel.class);
364         assertNotNull(row.getKey());
365         for (int i = 0; i < row.getCells().size(); i++) {
366           if (count == 0) {
367             assertEquals("aaa", Bytes.toString(row.getKey()));
368           }
369           if (count == 23) {
370             assertEquals("aax", Bytes.toString(row.getKey()));
371           }
372           count++;
373         }
374         jParser.skipChildren();
375       } else {
376         found = jParser.getCurrentToken() == JsonToken.START_ARRAY;
377       }
378     }
379     assertEquals(24, count);
380   }
381 
382   @Test
383   public void testSimpleScannerProtobuf() throws Exception {
384     StringBuilder builder = new StringBuilder();
385     builder.append("/*");
386     builder.append("?");
387     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
388     builder.append("&");
389     builder.append(Constants.SCAN_LIMIT + "=15");
390     Response response = client.get("/" + TABLE + builder.toString(),
391       Constants.MIMETYPE_PROTOBUF);
392     assertEquals(200, response.getCode());
393     assertEquals(Constants.MIMETYPE_PROTOBUF, response.getHeader("content-type"));
394     int rowCount = readProtobufStream(response.getStream());
395     assertEquals(15, rowCount);
396 
397     //Test with start row and end row.
398     builder = new StringBuilder();
399     builder.append("/*");
400     builder.append("?");
401     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
402     builder.append("&");
403     builder.append(Constants.SCAN_START_ROW + "=aaa");
404     builder.append("&");
405     builder.append(Constants.SCAN_END_ROW + "=aay");
406     response = client.get("/" + TABLE + builder.toString(),
407       Constants.MIMETYPE_PROTOBUF);
408     assertEquals(200, response.getCode());
409     assertEquals(Constants.MIMETYPE_PROTOBUF, response.getHeader("content-type"));
410     rowCount = readProtobufStream(response.getStream());
411     assertEquals(24, rowCount);
412   }
413 
414   private void checkRowsNotNull(CellSetModel model) {
415     for (RowModel row: model.getRows()) {
416       assertTrue(row.getKey() != null);
417       assertTrue(row.getCells().size() > 0);
418     }
419   }
420 
421   /**
422    * Read protobuf stream.
423    * @param inputStream the input stream
424    * @return The number of rows in the cell set model.
425    * @throws IOException Signals that an I/O exception has occurred.
426    */
427   public int readProtobufStream(InputStream inputStream) throws IOException{
428     DataInputStream stream = new DataInputStream(inputStream);
429     CellSetModel model = null;
430     int rowCount = 0;
431     try {
432       while (true) {
433         byte[] lengthBytes = new byte[2];
434         int readBytes = stream.read(lengthBytes);
435         if (readBytes == -1) {
436           break;
437         }
438         assertEquals(2, readBytes);
439         int length = Bytes.toShort(lengthBytes);
440         byte[] cellset = new byte[length];
441         stream.read(cellset);
442         model = new CellSetModel();
443         model.getObjectFromMessage(cellset);
444         checkRowsNotNull(model);
445         rowCount = rowCount + TestScannerResource.countCellSet(model);
446       }
447     } catch (EOFException exp) {
448       exp.printStackTrace();
449     } finally {
450       stream.close();
451     }
452     return rowCount;
453   }
454 
455   @Test
456   public void testScanningUnknownColumnJson() throws IOException {
457     // Test scanning particular columns with limit.
458     StringBuilder builder = new StringBuilder();
459     builder.append("/*");
460     builder.append("?");
461     builder.append(Constants.SCAN_COLUMN + "=a:test");
462     Response response = client.get("/" + TABLE  + builder.toString(),
463       Constants.MIMETYPE_JSON);
464     assertEquals(200, response.getCode());
465     assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
466     ObjectMapper mapper = new JacksonProvider().locateMapper(CellSetModel.class,
467       MediaType.APPLICATION_JSON_TYPE);
468     CellSetModel model = mapper.readValue(response.getStream(), CellSetModel.class);
469     int count = TestScannerResource.countCellSet(model);
470     assertEquals(0, count);
471   }
472 
473   @Test
474   public void testSimpleFilter() throws IOException, JAXBException {
475     StringBuilder builder = new StringBuilder();
476     builder.append("/*");
477     builder.append("?");
478     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
479     builder.append("&");
480     builder.append(Constants.SCAN_START_ROW + "=aaa");
481     builder.append("&");
482     builder.append(Constants.SCAN_END_ROW + "=aay");
483     builder.append("&");
484     builder.append(Constants.SCAN_FILTER + "=" + URLEncoder.encode("PrefixFilter('aab')", "UTF-8"));
485     Response response =
486         client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML);
487     assertEquals(200, response.getCode());
488     JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class);
489     Unmarshaller ush = ctx.createUnmarshaller();
490     CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream());
491     int count = TestScannerResource.countCellSet(model);
492     assertEquals(1, count);
493     assertEquals("aab", new String(model.getRows().get(0).getCells().get(0).getValue(),
494       StandardCharsets.UTF_8));
495   }
496 
497   @Test
498   public void testQualifierAndPrefixFilters() throws IOException, JAXBException {
499     StringBuilder builder = new StringBuilder();
500     builder.append("/abc*");
501     builder.append("?");
502     builder.append(Constants.SCAN_FILTER + "="
503         + URLEncoder.encode("QualifierFilter(=,'binary:1')", "UTF-8"));
504     Response response =
505         client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML);
506     assertEquals(200, response.getCode());
507     JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class);
508     Unmarshaller ush = ctx.createUnmarshaller();
509     CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream());
510     int count = TestScannerResource.countCellSet(model);
511     assertEquals(1, count);
512     assertEquals("abc", new String(model.getRows().get(0).getCells().get(0).getValue(),
513       StandardCharsets.UTF_8));
514   }
515 
516   @Test
517   public void testCompoundFilter() throws IOException, JAXBException {
518     StringBuilder builder = new StringBuilder();
519     builder.append("/*");
520     builder.append("?");
521     builder.append(Constants.SCAN_FILTER + "="
522         + URLEncoder.encode("PrefixFilter('abc') AND QualifierFilter(=,'binary:1')", "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", new String(model.getRows().get(0).getCells().get(0).getValue(),
532       StandardCharsets.UTF_8));
533   }
534 
535   @Test
536   public void testCustomFilter() throws IOException, JAXBException {
537     StringBuilder builder = new StringBuilder();
538     builder.append("/a*");
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     assertEquals(1, count);
551     assertEquals("abc", new String(model.getRows().get(0).getCells().get(0).getValue(),
552       StandardCharsets.UTF_8));
553   }
554 
555   @Test
556   public void testNegativeCustomFilter() throws IOException, JAXBException {
557     StringBuilder builder = new StringBuilder();
558     builder.append("/b*");
559     builder.append("?");
560     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
561     builder.append("&");
562     builder.append(Constants.SCAN_FILTER + "=" + URLEncoder.encode("CustomFilter('abc')", "UTF-8"));
563     Response response =
564         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     // Should return no rows as the filters conflict
571     assertEquals(0, count);
572   }
573 
574   @Test
575   public void testReversed() throws IOException, JAXBException {
576     StringBuilder builder = new StringBuilder();
577     builder.append("/*");
578     builder.append("?");
579     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
580     builder.append("&");
581     builder.append(Constants.SCAN_START_ROW + "=aaa");
582     builder.append("&");
583     builder.append(Constants.SCAN_END_ROW + "=aay");
584     Response response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML);
585     assertEquals(200, response.getCode());
586     JAXBContext ctx = JAXBContext.newInstance(CellSetModel.class);
587     Unmarshaller ush = ctx.createUnmarshaller();
588     CellSetModel model = (CellSetModel) ush.unmarshal(response.getStream());
589     int count = TestScannerResource.countCellSet(model);
590     assertEquals(24, count);
591     List<RowModel> rowModels = model.getRows().subList(1, count);
592 
593     //reversed
594     builder = new StringBuilder();
595     builder.append("/*");
596     builder.append("?");
597     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
598     builder.append("&");
599     builder.append(Constants.SCAN_START_ROW + "=aay");
600     builder.append("&");
601     builder.append(Constants.SCAN_END_ROW + "=aaa");
602     builder.append("&");
603     builder.append(Constants.SCAN_REVERSED + "=true");
604     response = client.get("/" + TABLE + builder.toString(), Constants.MIMETYPE_XML);
605     assertEquals(200, response.getCode());
606     model = (CellSetModel) ush.unmarshal(response.getStream());
607     count = TestScannerResource.countCellSet(model);
608     assertEquals(24, count);
609     List<RowModel> reversedRowModels = model.getRows().subList(1, count);
610 
611     Collections.reverse(reversedRowModels);
612     assertEquals(rowModels.size(), reversedRowModels.size());
613     for (int i = 0; i < rowModels.size(); i++) {
614       RowModel rowModel = rowModels.get(i);
615       RowModel reversedRowModel = reversedRowModels.get(i);
616 
617       assertEquals(new String(rowModel.getKey(), StandardCharsets.UTF_8),
618           new String(reversedRowModel.getKey(), StandardCharsets.UTF_8));
619       assertEquals(new String(rowModel.getCells().get(0).getValue(), StandardCharsets.UTF_8),
620           new String(reversedRowModel.getCells().get(0).getValue(), StandardCharsets.UTF_8));
621     }
622   }
623 
624   @Test
625   public void testColumnWithEmptyQualifier() throws IOException {
626     // Test scanning with empty qualifier
627     StringBuilder builder = new StringBuilder();
628     builder.append("/*");
629     builder.append("?");
630     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_EMPTY);
631     Response response = client.get("/" + TABLE + builder.toString(),
632         Constants.MIMETYPE_JSON);
633     assertEquals(200, response.getCode());
634     assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
635     ObjectMapper mapper = new JacksonProvider()
636         .locateMapper(CellSetModel.class, MediaType.APPLICATION_JSON_TYPE);
637     CellSetModel model = mapper.readValue(response.getStream(), CellSetModel.class);
638     int count = TestScannerResource.countCellSet(model);
639     assertEquals(expectedRows3, count);
640     checkRowsNotNull(model);
641     RowModel startRow = model.getRows().get(0);
642     assertEquals("aaa", Bytes.toString(startRow.getKey()));
643     assertEquals(1, startRow.getCells().size());
644 
645     // Test scanning with empty qualifier and normal qualifier
646     builder = new StringBuilder();
647     builder.append("/*");
648     builder.append("?");
649     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_1);
650     builder.append("&");
651     builder.append(Constants.SCAN_COLUMN + "=" + COLUMN_EMPTY);
652     response = client.get("/" + TABLE + builder.toString(),
653         Constants.MIMETYPE_JSON);
654     assertEquals(200, response.getCode());
655     assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
656     mapper = new JacksonProvider()
657         .locateMapper(CellSetModel.class, MediaType.APPLICATION_JSON_TYPE);
658     model = mapper.readValue(response.getStream(), CellSetModel.class);
659     count = TestScannerResource.countCellSet(model);
660     assertEquals(expectedRows1 + expectedRows3, count);
661     checkRowsNotNull(model);
662   }
663 
664   public static class CustomFilter extends PrefixFilter {
665     private byte[] key = null;
666 
667     public CustomFilter(byte[] key) {
668       super(key);
669     }
670 
671     @Override
672     public boolean filterRowKey(byte[] buffer, int offset, int length) {
673       int cmp = Bytes.compareTo(buffer, offset, length, this.key, 0, this.key.length);
674       return cmp != 0;
675     }
676 
677     public static Filter createFilterFromArguments(ArrayList<byte[]> filterArguments) {
678       byte[] prefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
679       return new CustomFilter(prefix);
680     }
681   }
682 
683   /**
684    * The Class ClientSideCellSetModel which mimics cell set model, and contains listener to perform
685    * user defined operations on the row model.
686    */
687   @XmlRootElement(name = "CellSet")
688   @XmlAccessorType(XmlAccessType.FIELD)
689   public static class ClientSideCellSetModel implements Serializable {
690     private static final long serialVersionUID = 1L;
691 
692     /**
693      * This list is not a real list; instead it will notify a listener whenever JAXB has
694      * unmarshalled the next row.
695      */
696     @XmlElement(name="Row")
697     private List<RowModel> row;
698 
699     static boolean listenerInvoked = false;
700 
701     /**
702      * Install a listener for row model on this object. If l is null, the listener
703      * is removed again.
704      */
705     public void setCellSetModelListener(final Listener l) {
706       row = (l == null) ? null : new ArrayList<RowModel>() {
707         private static final long serialVersionUID = 1L;
708 
709         @Override
710         public boolean add(RowModel o) {
711           l.handleRowModel(ClientSideCellSetModel.this, o);
712           listenerInvoked = true;
713           return false;
714         }
715       };
716     }
717 
718     /**
719      * This listener is invoked every time a new row model is unmarshalled.
720      */
721     public interface Listener {
722       void handleRowModel(ClientSideCellSetModel helper, RowModel rowModel);
723     }
724   }
725 }