View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.rest.model;
21  
22  import java.io.IOException;
23  import java.io.Serializable;
24  import java.io.StringReader;
25  import java.io.StringWriter;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.NavigableSet;
30  
31  import javax.xml.bind.annotation.XmlAttribute;
32  import javax.xml.bind.annotation.XmlElement;
33  import javax.xml.bind.annotation.XmlRootElement;
34  
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.classification.InterfaceAudience;
37  import org.apache.hadoop.hbase.client.Scan;
38  import org.apache.hadoop.hbase.filter.BinaryComparator;
39  import org.apache.hadoop.hbase.filter.BinaryPrefixComparator;
40  import org.apache.hadoop.hbase.filter.BitComparator;
41  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
42  import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
43  import org.apache.hadoop.hbase.filter.ColumnPaginationFilter;
44  import org.apache.hadoop.hbase.filter.ColumnPrefixFilter;
45  import org.apache.hadoop.hbase.filter.ColumnRangeFilter;
46  import org.apache.hadoop.hbase.filter.CompareFilter;
47  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
48  import org.apache.hadoop.hbase.filter.DependentColumnFilter;
49  import org.apache.hadoop.hbase.filter.FamilyFilter;
50  import org.apache.hadoop.hbase.filter.Filter;
51  import org.apache.hadoop.hbase.filter.FilterList;
52  import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
53  import org.apache.hadoop.hbase.filter.InclusiveStopFilter;
54  import org.apache.hadoop.hbase.filter.KeyOnlyFilter;
55  import org.apache.hadoop.hbase.filter.MultiRowRangeFilter;
56  import org.apache.hadoop.hbase.filter.MultiRowRangeFilter.RowRange;
57  import org.apache.hadoop.hbase.filter.MultipleColumnPrefixFilter;
58  import org.apache.hadoop.hbase.filter.NullComparator;
59  import org.apache.hadoop.hbase.filter.PageFilter;
60  import org.apache.hadoop.hbase.filter.PrefixFilter;
61  import org.apache.hadoop.hbase.filter.QualifierFilter;
62  import org.apache.hadoop.hbase.filter.RandomRowFilter;
63  import org.apache.hadoop.hbase.filter.RegexStringComparator;
64  import org.apache.hadoop.hbase.filter.RowFilter;
65  import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter;
66  import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
67  import org.apache.hadoop.hbase.filter.SkipFilter;
68  import org.apache.hadoop.hbase.filter.SubstringComparator;
69  import org.apache.hadoop.hbase.filter.TimestampsFilter;
70  import org.apache.hadoop.hbase.filter.ValueFilter;
71  import org.apache.hadoop.hbase.filter.WhileMatchFilter;
72  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
73  import org.apache.hadoop.hbase.rest.ProtobufMessageHandler;
74  import org.apache.hadoop.hbase.rest.protobuf.generated.ScannerMessage.Scanner;
75  import org.apache.hadoop.hbase.security.visibility.Authorizations;
76  import org.apache.hadoop.hbase.util.Base64;
77  import org.apache.hadoop.hbase.util.ByteStringer;
78  import org.apache.hadoop.hbase.util.Bytes;
79  
80  import com.google.protobuf.ByteString;
81  import com.sun.jersey.api.json.JSONConfiguration;
82  import com.sun.jersey.api.json.JSONJAXBContext;
83  import com.sun.jersey.api.json.JSONMarshaller;
84  import com.sun.jersey.api.json.JSONUnmarshaller;
85  
86  /**
87   * A representation of Scanner parameters.
88   * 
89   * <pre>
90   * &lt;complexType name="Scanner"&gt;
91   *   &lt;sequence>
92   *     &lt;element name="column" type="base64Binary" minOccurs="0" maxOccurs="unbounded"/&gt;
93   *     &lt;element name="filter" type="string" minOccurs="0" maxOccurs="1"&gt;&lt;/element&gt;
94   *   &lt;/sequence&gt;
95   *   &lt;attribute name="startRow" type="base64Binary"&gt;&lt;/attribute&gt;
96   *   &lt;attribute name="endRow" type="base64Binary"&gt;&lt;/attribute&gt;
97   *   &lt;attribute name="batch" type="int"&gt;&lt;/attribute&gt;
98   *   &lt;attribute name="caching" type="int"&gt;&lt;/attribute&gt;
99   *   &lt;attribute name="startTime" type="int"&gt;&lt;/attribute&gt;
100  *   &lt;attribute name="endTime" type="int"&gt;&lt;/attribute&gt;
101  *   &lt;attribute name="maxVersions" type="int"&gt;&lt;/attribute&gt;
102  * &lt;/complexType&gt;
103  * </pre>
104  */
105 @XmlRootElement(name="Scanner")
106 @InterfaceAudience.Private
107 public class ScannerModel implements ProtobufMessageHandler, Serializable {
108 
109   private static final long serialVersionUID = 1L;
110 
111   private byte[] startRow = HConstants.EMPTY_START_ROW;
112   private byte[] endRow = HConstants.EMPTY_END_ROW;;
113   private List<byte[]> columns = new ArrayList<byte[]>();
114   private int batch = Integer.MAX_VALUE;
115   private long startTime = 0;
116   private long endTime = Long.MAX_VALUE;
117   private String filter = null;
118   private int maxVersions = Integer.MAX_VALUE;
119   private int caching = -1;
120   private List<String> labels = new ArrayList<String>();
121   private boolean cacheBlocks = true;
122   
123   @XmlRootElement
124   static class FilterModel {
125     
126     @XmlRootElement
127     static class ByteArrayComparableModel {
128       @XmlAttribute public String type;
129       @XmlAttribute public String value;
130       @XmlAttribute public String op;
131 
132       static enum ComparatorType {
133         BinaryComparator,
134         BinaryPrefixComparator,
135         BitComparator,
136         NullComparator,
137         RegexStringComparator,
138         SubstringComparator    
139       }
140 
141       public ByteArrayComparableModel() { }
142 
143       public ByteArrayComparableModel(
144           ByteArrayComparable comparator) {
145         String typeName = comparator.getClass().getSimpleName();
146         ComparatorType type = ComparatorType.valueOf(typeName);
147         this.type = typeName;
148         switch (type) {
149           case BinaryComparator:
150           case BinaryPrefixComparator:
151             this.value = Base64.encodeBytes(comparator.getValue());
152             break;
153           case BitComparator:
154             this.value = Base64.encodeBytes(comparator.getValue());
155             this.op = ((BitComparator)comparator).getOperator().toString();
156             break;
157           case NullComparator:
158             break;
159           case RegexStringComparator:
160           case SubstringComparator:
161             this.value = Bytes.toString(comparator.getValue());
162             break;
163           default:
164             throw new RuntimeException("unhandled filter type: " + type);
165         }
166       }
167 
168       public ByteArrayComparable build() {
169         ByteArrayComparable comparator;
170         switch (ComparatorType.valueOf(type)) {
171           case BinaryComparator:
172             comparator = new BinaryComparator(Base64.decode(value));
173             break;
174           case BinaryPrefixComparator:
175             comparator = new BinaryPrefixComparator(Base64.decode(value));
176             break;
177           case BitComparator:
178             comparator = new BitComparator(Base64.decode(value),
179                 BitComparator.BitwiseOp.valueOf(op));
180             break;
181           case NullComparator:
182             comparator = new NullComparator();
183             break;
184           case RegexStringComparator:
185             comparator = new RegexStringComparator(value);
186             break;
187           case SubstringComparator:
188             comparator = new SubstringComparator(value);
189             break;
190           default:
191             throw new RuntimeException("unhandled comparator type: " + type);
192         }
193         return comparator;
194       }
195 
196     }
197 
198     // A grab bag of fields, would have been a union if this were C.
199     // These are null by default and will only be serialized if set (non null).
200     @XmlAttribute public String type;
201     @XmlAttribute public String op;
202     @XmlElement ByteArrayComparableModel comparator;
203     @XmlAttribute public String value;
204     @XmlElement public List<FilterModel> filters;
205     @XmlAttribute public Integer limit;
206     @XmlAttribute public Integer offset;
207     @XmlAttribute public String family;
208     @XmlAttribute public String qualifier;
209     @XmlAttribute public Boolean ifMissing;
210     @XmlAttribute public Boolean latestVersion;
211     @XmlAttribute public String minColumn;
212     @XmlAttribute public Boolean minColumnInclusive;
213     @XmlAttribute public String maxColumn;
214     @XmlAttribute public Boolean maxColumnInclusive;
215     @XmlAttribute public Boolean dropDependentColumn;
216     @XmlAttribute public Float chance;
217     @XmlElement public List<String> prefixes;
218     @XmlElement private List<RowRange> ranges;
219     @XmlElement public List<Long> timestamps;
220 
221     static enum FilterType {
222       ColumnCountGetFilter,
223       ColumnPaginationFilter,
224       ColumnPrefixFilter,
225       ColumnRangeFilter,
226       DependentColumnFilter,
227       FamilyFilter,
228       FilterList,
229       FirstKeyOnlyFilter,
230       InclusiveStopFilter,
231       KeyOnlyFilter,
232       MultipleColumnPrefixFilter,
233       MultiRowRangeFilter,
234       PageFilter,
235       PrefixFilter,
236       QualifierFilter,
237       RandomRowFilter,
238       RowFilter,
239       SingleColumnValueExcludeFilter,
240       SingleColumnValueFilter,
241       SkipFilter,
242       TimestampsFilter,
243       ValueFilter,
244       WhileMatchFilter    
245     }
246 
247     public FilterModel() { }
248     
249     public FilterModel(Filter filter) { 
250       String typeName = filter.getClass().getSimpleName();
251       FilterType type = FilterType.valueOf(typeName);
252       this.type = typeName;
253       switch (type) {
254         case ColumnCountGetFilter:
255           this.limit = ((ColumnCountGetFilter)filter).getLimit();
256           break;
257         case ColumnPaginationFilter:
258           this.limit = ((ColumnPaginationFilter)filter).getLimit();
259           this.offset = ((ColumnPaginationFilter)filter).getOffset();
260           break;
261         case ColumnPrefixFilter:
262           this.value = Base64.encodeBytes(((ColumnPrefixFilter)filter).getPrefix());
263           break;
264         case ColumnRangeFilter:
265           this.minColumn = Base64.encodeBytes(((ColumnRangeFilter)filter).getMinColumn());
266           this.minColumnInclusive = ((ColumnRangeFilter)filter).getMinColumnInclusive();
267           this.maxColumn = Base64.encodeBytes(((ColumnRangeFilter)filter).getMaxColumn());
268           this.maxColumnInclusive = ((ColumnRangeFilter)filter).getMaxColumnInclusive();
269           break;
270         case DependentColumnFilter: {
271           DependentColumnFilter dcf = (DependentColumnFilter)filter;
272           this.family = Base64.encodeBytes(dcf.getFamily());
273           byte[] qualifier = dcf.getQualifier();
274           if (qualifier != null) {
275             this.qualifier = Base64.encodeBytes(qualifier);
276           }
277           this.op = dcf.getOperator().toString();
278           this.comparator = new ByteArrayComparableModel(dcf.getComparator());
279           this.dropDependentColumn = dcf.dropDependentColumn();
280         } break;
281         case FilterList:
282           this.op = ((FilterList)filter).getOperator().toString();
283           this.filters = new ArrayList<FilterModel>();
284           for (Filter child: ((FilterList)filter).getFilters()) {
285             this.filters.add(new FilterModel(child));
286           }
287           break;
288         case FirstKeyOnlyFilter:
289         case KeyOnlyFilter:
290           break;
291         case InclusiveStopFilter:
292           this.value = 
293             Base64.encodeBytes(((InclusiveStopFilter)filter).getStopRowKey());
294           break;
295         case MultipleColumnPrefixFilter:
296           this.prefixes = new ArrayList<String>();
297           for (byte[] prefix: ((MultipleColumnPrefixFilter)filter).getPrefix()) {
298             this.prefixes.add(Base64.encodeBytes(prefix));
299           }
300           break;
301         case MultiRowRangeFilter:
302           this.ranges = new ArrayList<RowRange>();
303           for(RowRange range : ((MultiRowRangeFilter)filter).getRowRanges()) {
304             this.ranges.add(new RowRange(range.getStartRow(), range.isStartRowInclusive(),
305                 range.getStopRow(), range.isStopRowInclusive()));
306           }
307           break;
308         case PageFilter:
309           this.value = Long.toString(((PageFilter)filter).getPageSize());
310           break;
311         case PrefixFilter:
312           this.value = Base64.encodeBytes(((PrefixFilter)filter).getPrefix());
313           break;
314         case FamilyFilter:
315         case QualifierFilter:
316         case RowFilter:
317         case ValueFilter:
318           this.op = ((CompareFilter)filter).getOperator().toString();
319           this.comparator = 
320             new ByteArrayComparableModel(
321               ((CompareFilter)filter).getComparator());
322           break;
323         case RandomRowFilter:
324           this.chance = ((RandomRowFilter)filter).getChance();
325           break;
326         case SingleColumnValueExcludeFilter:
327         case SingleColumnValueFilter: {
328           SingleColumnValueFilter scvf = (SingleColumnValueFilter) filter;
329           this.family = Base64.encodeBytes(scvf.getFamily());
330           byte[] qualifier = scvf.getQualifier();
331           if (qualifier != null) {
332             this.qualifier = Base64.encodeBytes(qualifier);
333           }
334           this.op = scvf.getOperator().toString();
335           this.comparator = 
336             new ByteArrayComparableModel(scvf.getComparator());
337           if (scvf.getFilterIfMissing()) {
338             this.ifMissing = true;
339           }
340           if (scvf.getLatestVersionOnly()) {
341             this.latestVersion = true;
342           }
343         } break;
344         case SkipFilter:
345           this.filters = new ArrayList<FilterModel>();
346           this.filters.add(new FilterModel(((SkipFilter)filter).getFilter()));
347           break;
348         case TimestampsFilter:
349           this.timestamps = ((TimestampsFilter)filter).getTimestamps();
350           break;
351         case WhileMatchFilter:
352           this.filters = new ArrayList<FilterModel>();
353           this.filters.add(
354             new FilterModel(((WhileMatchFilter)filter).getFilter()));
355           break;
356         default:
357           throw new RuntimeException("unhandled filter type " + type);
358       }
359     }
360 
361     public Filter build() {
362       Filter filter;
363       switch (FilterType.valueOf(type)) {
364       case ColumnCountGetFilter:
365         filter = new ColumnCountGetFilter(limit);
366         break;
367       case ColumnPaginationFilter:
368         filter = new ColumnPaginationFilter(limit, offset);
369         break;
370       case ColumnPrefixFilter:
371         filter = new ColumnPrefixFilter(Base64.decode(value));
372         break;
373       case ColumnRangeFilter:
374         filter = new ColumnRangeFilter(Base64.decode(minColumn),
375             minColumnInclusive, Base64.decode(maxColumn),
376             maxColumnInclusive);
377         break;
378       case DependentColumnFilter:
379         filter = new DependentColumnFilter(Base64.decode(family),
380             qualifier != null ? Base64.decode(qualifier) : null,
381             dropDependentColumn, CompareOp.valueOf(op), comparator.build());
382         break;
383       case FamilyFilter:
384         filter = new FamilyFilter(CompareOp.valueOf(op), comparator.build());
385         break;
386       case FilterList: {
387         List<Filter> list = new ArrayList<Filter>();
388         for (FilterModel model: filters) {
389           list.add(model.build());
390         }
391         filter = new FilterList(FilterList.Operator.valueOf(op), list);
392       } break;
393       case FirstKeyOnlyFilter:
394         filter = new FirstKeyOnlyFilter();
395         break;
396       case InclusiveStopFilter:
397         filter = new InclusiveStopFilter(Base64.decode(value));
398         break;
399       case KeyOnlyFilter:
400         filter = new KeyOnlyFilter();
401         break;
402       case MultipleColumnPrefixFilter: {
403         byte[][] values = new byte[prefixes.size()][];
404         for (int i = 0; i < prefixes.size(); i++) {
405           values[i] = Base64.decode(prefixes.get(i));
406         }
407         filter = new MultipleColumnPrefixFilter(values);
408       } break;
409       case MultiRowRangeFilter: {
410         try {
411           filter = new MultiRowRangeFilter(ranges);
412         } catch (IOException e) {
413           throw new RuntimeException(e);
414         }
415       } break;
416       case PageFilter:
417         filter = new PageFilter(Long.valueOf(value));
418         break;
419       case PrefixFilter:
420         filter = new PrefixFilter(Base64.decode(value));
421         break;
422       case QualifierFilter:
423         filter = new QualifierFilter(CompareOp.valueOf(op), comparator.build());
424         break;
425       case RandomRowFilter:
426         filter = new RandomRowFilter(chance);
427         break;
428       case RowFilter:
429         filter = new RowFilter(CompareOp.valueOf(op), comparator.build());
430         break;
431       case SingleColumnValueFilter:
432         filter = new SingleColumnValueFilter(Base64.decode(family),
433           qualifier != null ? Base64.decode(qualifier) : null,
434           CompareOp.valueOf(op), comparator.build());
435         if (ifMissing != null) {
436           ((SingleColumnValueFilter)filter).setFilterIfMissing(ifMissing);
437         }
438         if (latestVersion != null) {
439           ((SingleColumnValueFilter)filter).setLatestVersionOnly(latestVersion);
440         }
441         break;
442       case SingleColumnValueExcludeFilter:
443         filter = new SingleColumnValueExcludeFilter(Base64.decode(family),
444           qualifier != null ? Base64.decode(qualifier) : null,
445           CompareOp.valueOf(op), comparator.build());
446         if (ifMissing != null) {
447           ((SingleColumnValueExcludeFilter)filter).setFilterIfMissing(ifMissing);
448         }
449         if (latestVersion != null) {
450           ((SingleColumnValueExcludeFilter)filter).setLatestVersionOnly(latestVersion);
451         }
452         break;
453       case SkipFilter:
454         filter = new SkipFilter(filters.get(0).build());
455         break;
456       case TimestampsFilter:
457         filter = new TimestampsFilter(timestamps);
458         break;
459       case ValueFilter:
460         filter = new ValueFilter(CompareOp.valueOf(op), comparator.build());
461         break;
462       case WhileMatchFilter:
463         filter = new WhileMatchFilter(filters.get(0).build());
464         break;
465       default:
466         throw new RuntimeException("unhandled filter type: " + type);
467       }
468       return filter;
469     }
470 
471   }
472 
473   /**
474    * @param s the JSON representation of the filter
475    * @return the filter
476    * @throws Exception
477    */
478   public static Filter buildFilter(String s) throws Exception {
479     JSONJAXBContext context =
480       new JSONJAXBContext(JSONConfiguration.natural().build(),
481         FilterModel.class);
482     JSONUnmarshaller unmarshaller = context.createJSONUnmarshaller();
483     FilterModel model = unmarshaller.unmarshalFromJSON(new StringReader(s),
484       FilterModel.class);
485     return model.build();
486   }
487 
488   /**
489    * @param filter the filter
490    * @return the JSON representation of the filter
491    * @throws Exception 
492    */
493   public static String stringifyFilter(final Filter filter) throws Exception {
494     JSONJAXBContext context =
495       new JSONJAXBContext(JSONConfiguration.natural().build(),
496         FilterModel.class);
497     JSONMarshaller marshaller = context.createJSONMarshaller();
498     StringWriter writer = new StringWriter();
499     marshaller.marshallToJSON(new FilterModel(filter), writer);
500     return writer.toString();
501   }
502 
503   private static final byte[] COLUMN_DIVIDER = Bytes.toBytes(":");
504 
505   /**
506    * @param scan the scan specification
507    * @throws Exception 
508    */
509   public static ScannerModel fromScan(Scan scan) throws Exception {
510     ScannerModel model = new ScannerModel();
511     model.setStartRow(scan.getStartRow());
512     model.setEndRow(scan.getStopRow());
513     Map<byte [], NavigableSet<byte []>> families = scan.getFamilyMap();
514     if (families != null) {
515       for (Map.Entry<byte [], NavigableSet<byte []>> entry : families.entrySet()) {
516         if (entry.getValue() != null) {
517           for (byte[] qualifier: entry.getValue()) {
518             model.addColumn(Bytes.add(entry.getKey(), COLUMN_DIVIDER, qualifier));
519           }
520         } else {
521           model.addColumn(entry.getKey());
522         }
523       }
524     }
525     model.setStartTime(scan.getTimeRange().getMin());
526     model.setEndTime(scan.getTimeRange().getMax());
527     int caching = scan.getCaching();
528     if (caching > 0) {
529       model.setCaching(caching);
530     }
531     int batch = scan.getBatch();
532     if (batch > 0) {
533       model.setBatch(batch);
534     }
535     int maxVersions = scan.getMaxVersions();
536     if (maxVersions > 0) {
537       model.setMaxVersions(maxVersions);
538     }
539     Filter filter = scan.getFilter();
540     if (filter != null) {
541       model.setFilter(stringifyFilter(filter));
542     }
543     // Add the visbility labels if found in the attributes
544     Authorizations authorizations = scan.getAuthorizations();
545     if (authorizations != null) {
546       List<String> labels = authorizations.getLabels();
547       for (String label : labels) {
548         model.addLabel(label);
549       }
550     }
551     return model;
552   }
553 
554   /**
555    * Default constructor
556    */
557   public ScannerModel() {}
558 
559   /**
560    * Constructor
561    * @param startRow the start key of the row-range
562    * @param endRow the end key of the row-range
563    * @param columns the columns to scan
564    * @param batch the number of values to return in batch
565    * @param caching the number of rows that the scanner will fetch at once
566    * @param endTime the upper bound on timestamps of values of interest
567    * @param maxVersions the maximum number of versions to return
568    * @param filter a filter specification
569    * (values with timestamps later than this are excluded)
570    */
571   public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns,
572       int batch, int caching, long endTime, int maxVersions, String filter) {
573     super();
574     this.startRow = startRow;
575     this.endRow = endRow;
576     this.columns = columns;
577     this.batch = batch;
578     this.caching = caching;
579     this.endTime = endTime;
580     this.maxVersions = maxVersions;
581     this.filter = filter;
582   }
583 
584   /**
585    * Constructor 
586    * @param startRow the start key of the row-range
587    * @param endRow the end key of the row-range
588    * @param columns the columns to scan
589    * @param batch the number of values to return in batch
590    * @param caching the number of rows that the scanner will fetch at once
591    * @param startTime the lower bound on timestamps of values of interest
592    * (values with timestamps earlier than this are excluded)
593    * @param endTime the upper bound on timestamps of values of interest
594    * (values with timestamps later than this are excluded)
595    * @param filter a filter specification
596    */
597   public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns,
598       int batch, int caching, long startTime, long endTime, String filter) {
599     super();
600     this.startRow = startRow;
601     this.endRow = endRow;
602     this.columns = columns;
603     this.batch = batch;
604     this.caching = caching;
605     this.startTime = startTime;
606     this.endTime = endTime;
607     this.filter = filter;
608   }
609 
610   /**
611    * Add a column to the column set
612    * @param column the column name, as &lt;column&gt;(:&lt;qualifier&gt;)?
613    */
614   public void addColumn(byte[] column) {
615     columns.add(column);
616   }
617   
618   /**
619    * Add a visibility label to the scan
620    */
621   public void addLabel(String label) {
622     labels.add(label);
623   }
624   /**
625    * @return true if a start row was specified
626    */
627   public boolean hasStartRow() {
628     return !Bytes.equals(startRow, HConstants.EMPTY_START_ROW);
629   }
630 
631   /**
632    * @return start row
633    */
634   @XmlAttribute
635   public byte[] getStartRow() {
636     return startRow;
637   }
638 
639   /**
640    * @return true if an end row was specified
641    */
642   public boolean hasEndRow() {
643     return !Bytes.equals(endRow, HConstants.EMPTY_END_ROW);
644   }
645 
646   /**
647    * @return end row
648    */
649   @XmlAttribute
650   public byte[] getEndRow() {
651     return endRow;
652   }
653 
654   /**
655    * @return list of columns of interest in column:qualifier format, or empty for all
656    */
657   @XmlElement(name="column")
658   public List<byte[]> getColumns() {
659     return columns;
660   }
661   
662   @XmlElement(name="labels")
663   public List<String> getLabels() {
664     return labels;
665   }
666 
667   /**
668    * @return the number of cells to return in batch
669    */
670   @XmlAttribute
671   public int getBatch() {
672     return batch;
673   }
674 
675   /**
676    * @return the number of rows that the scanner to fetch at once
677    */
678   @XmlAttribute
679   public int getCaching() {
680     return caching;
681   }
682 
683   /**
684    * @return true if HFile blocks should be cached on the servers for this scan, false otherwise
685    */
686   @XmlAttribute
687   public boolean getCacheBlocks() {
688     return cacheBlocks;
689   }
690 
691   /**
692    * @return the lower bound on timestamps of items of interest
693    */
694   @XmlAttribute
695   public long getStartTime() {
696     return startTime;
697   }
698 
699   /**
700    * @return the upper bound on timestamps of items of interest
701    */
702   @XmlAttribute
703   public long getEndTime() {
704     return endTime;
705   }
706 
707   /**
708    * @return maximum number of versions to return
709    */
710   @XmlAttribute
711   public int getMaxVersions() {
712     return maxVersions;
713   }
714 
715   /**
716    * @return the filter specification
717    */
718   @XmlElement
719   public String getFilter() {
720     return filter;
721   }
722 
723   /**
724    * @param startRow start row
725    */
726   public void setStartRow(byte[] startRow) {
727     this.startRow = startRow;
728   }
729 
730   /**
731    * @param endRow end row
732    */
733   public void setEndRow(byte[] endRow) {
734     this.endRow = endRow;
735   }
736 
737   /**
738    * @param columns list of columns of interest in column:qualifier format, or empty for all
739    */
740   public void setColumns(List<byte[]> columns) {
741     this.columns = columns;
742   }
743 
744   /**
745    * @param batch the number of cells to return in batch
746    */
747   public void setBatch(int batch) {
748     this.batch = batch;
749   }
750 
751   /**
752    * @param caching the number of rows to fetch at once
753    */
754   public void setCaching(int caching) {
755     this.caching = caching;
756   }
757 
758   /**
759    * @param value true if HFile blocks should be cached on the servers for this scan, false otherwise
760    */
761   public void setCacheBlocks(boolean value) {
762     this.cacheBlocks = value;
763   }
764 
765   /**
766    * @param maxVersions maximum number of versions to return
767    */
768   public void setMaxVersions(int maxVersions) {
769     this.maxVersions = maxVersions;
770   }
771 
772   /**
773    * @param startTime the lower bound on timestamps of values of interest
774    */
775   public void setStartTime(long startTime) {
776     this.startTime = startTime;
777   }
778 
779   /**
780    * @param endTime the upper bound on timestamps of values of interest
781    */
782   public void setEndTime(long endTime) {
783     this.endTime = endTime;
784   }
785 
786   /**
787    * @param filter the filter specification
788    */
789   public void setFilter(String filter) {
790     this.filter = filter;
791   }
792 
793   @Override
794   public byte[] createProtobufOutput() {
795     Scanner.Builder builder = Scanner.newBuilder();
796     if (!Bytes.equals(startRow, HConstants.EMPTY_START_ROW)) {
797       builder.setStartRow(ByteStringer.wrap(startRow));
798     }
799     if (!Bytes.equals(endRow, HConstants.EMPTY_START_ROW)) {
800       builder.setEndRow(ByteStringer.wrap(endRow));
801     }
802     for (byte[] column: columns) {
803       builder.addColumns(ByteStringer.wrap(column));
804     }
805     if (startTime != 0) {
806       builder.setStartTime(startTime);
807     }
808     if (endTime != 0) {
809       builder.setEndTime(endTime);
810     }
811     builder.setBatch(getBatch());
812     if (caching > 0) {
813       builder.setCaching(caching);
814     }
815     builder.setMaxVersions(maxVersions);
816     if (filter != null) {
817       builder.setFilter(filter);
818     }
819     if (labels != null && labels.size() > 0) {
820       for (String label : labels)
821         builder.addLabels(label);
822     }
823     builder.setCacheBlocks(cacheBlocks);
824     return builder.build().toByteArray();
825   }
826 
827   @Override
828   public ProtobufMessageHandler getObjectFromMessage(byte[] message)
829       throws IOException {
830     Scanner.Builder builder = Scanner.newBuilder();
831     ProtobufUtil.mergeFrom(builder, message);
832     if (builder.hasStartRow()) {
833       startRow = builder.getStartRow().toByteArray();
834     }
835     if (builder.hasEndRow()) {
836       endRow = builder.getEndRow().toByteArray();
837     }
838     for (ByteString column: builder.getColumnsList()) {
839       addColumn(column.toByteArray());
840     }
841     if (builder.hasBatch()) {
842       batch = builder.getBatch();
843     }
844     if (builder.hasCaching()) {
845       caching = builder.getCaching();
846     }
847     if (builder.hasStartTime()) {
848       startTime = builder.getStartTime();
849     }
850     if (builder.hasEndTime()) {
851       endTime = builder.getEndTime();
852     }
853     if (builder.hasMaxVersions()) {
854       maxVersions = builder.getMaxVersions();
855     }
856     if (builder.hasFilter()) {
857       filter = builder.getFilter();
858     }
859     if (builder.getLabelsList() != null) {
860       List<String> labels = builder.getLabelsList();
861       for(String label :  labels) {
862         addLabel(label);
863       }
864     }
865     if (builder.hasCacheBlocks()) {
866       this.cacheBlocks = builder.getCacheBlocks();
867     }
868     return this;
869   }
870 
871 }