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