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