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