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