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