001/*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.rest.model;
020
021import java.io.IOException;
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Base64;
025import java.util.List;
026import java.util.Map;
027import java.util.NavigableSet;
028
029import javax.ws.rs.core.MediaType;
030import javax.xml.bind.annotation.XmlAttribute;
031import javax.xml.bind.annotation.XmlElement;
032import javax.xml.bind.annotation.XmlRootElement;
033
034import org.apache.hadoop.hbase.CompareOperator;
035import org.apache.hadoop.hbase.HConstants;
036import org.apache.yetus.audience.InterfaceAudience;
037import org.apache.hadoop.hbase.client.Scan;
038import org.apache.hadoop.hbase.filter.BinaryComparator;
039import org.apache.hadoop.hbase.filter.BinaryPrefixComparator;
040import org.apache.hadoop.hbase.filter.BitComparator;
041import org.apache.hadoop.hbase.filter.ByteArrayComparable;
042import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
043import org.apache.hadoop.hbase.filter.ColumnPaginationFilter;
044import org.apache.hadoop.hbase.filter.ColumnPrefixFilter;
045import org.apache.hadoop.hbase.filter.ColumnRangeFilter;
046import org.apache.hadoop.hbase.filter.CompareFilter;
047import org.apache.hadoop.hbase.filter.DependentColumnFilter;
048import org.apache.hadoop.hbase.filter.FamilyFilter;
049import org.apache.hadoop.hbase.filter.Filter;
050import org.apache.hadoop.hbase.filter.FilterList;
051import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
052import org.apache.hadoop.hbase.filter.InclusiveStopFilter;
053import org.apache.hadoop.hbase.filter.KeyOnlyFilter;
054import org.apache.hadoop.hbase.filter.MultiRowRangeFilter;
055import org.apache.hadoop.hbase.filter.MultiRowRangeFilter.RowRange;
056import org.apache.hadoop.hbase.filter.MultipleColumnPrefixFilter;
057import org.apache.hadoop.hbase.filter.NullComparator;
058import org.apache.hadoop.hbase.filter.PageFilter;
059import org.apache.hadoop.hbase.filter.PrefixFilter;
060import org.apache.hadoop.hbase.filter.QualifierFilter;
061import org.apache.hadoop.hbase.filter.RandomRowFilter;
062import org.apache.hadoop.hbase.filter.RegexStringComparator;
063import org.apache.hadoop.hbase.filter.RowFilter;
064import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter;
065import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
066import org.apache.hadoop.hbase.filter.SkipFilter;
067import org.apache.hadoop.hbase.filter.SubstringComparator;
068import org.apache.hadoop.hbase.filter.TimestampsFilter;
069import org.apache.hadoop.hbase.filter.ValueFilter;
070import org.apache.hadoop.hbase.filter.WhileMatchFilter;
071import org.apache.hadoop.hbase.rest.ProtobufMessageHandler;
072import org.apache.hadoop.hbase.security.visibility.Authorizations;
073import org.apache.hadoop.hbase.util.Bytes;
074
075import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
076import org.apache.hadoop.hbase.shaded.rest.protobuf.generated.ScannerMessage.Scanner;
077
078import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
079import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
080
081import com.fasterxml.jackson.annotation.JsonInclude;
082import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
083
084/**
085 * A representation of Scanner parameters.
086 *
087 * <pre>
088 * &lt;complexType name="Scanner"&gt;
089 *   &lt;sequence&gt;
090 *     &lt;element name="column" type="base64Binary" minOccurs="0" maxOccurs="unbounded"/&gt;
091 *     &lt;element name="filter" type="string" minOccurs="0" maxOccurs="1"&gt;&lt;/element&gt;
092 *   &lt;/sequence&gt;
093 *   &lt;attribute name="startRow" type="base64Binary"&gt;&lt;/attribute&gt;
094 *   &lt;attribute name="endRow" type="base64Binary"&gt;&lt;/attribute&gt;
095 *   &lt;attribute name="batch" type="int"&gt;&lt;/attribute&gt;
096 *   &lt;attribute name="caching" type="int"&gt;&lt;/attribute&gt;
097 *   &lt;attribute name="startTime" type="int"&gt;&lt;/attribute&gt;
098 *   &lt;attribute name="endTime" type="int"&gt;&lt;/attribute&gt;
099 *   &lt;attribute name="maxVersions" type="int"&gt;&lt;/attribute&gt;
100 * &lt;/complexType&gt;
101 * </pre>
102 */
103@XmlRootElement(name="Scanner")
104@JsonInclude(JsonInclude.Include.NON_NULL)
105@InterfaceAudience.Private
106public class ScannerModel implements ProtobufMessageHandler, Serializable {
107
108  private static final long serialVersionUID = 1L;
109
110  private byte[] startRow = HConstants.EMPTY_START_ROW;
111  private byte[] endRow = HConstants.EMPTY_END_ROW;
112  private List<byte[]> columns = new ArrayList<>();
113  private int batch = Integer.MAX_VALUE;
114  private long startTime = 0;
115  private long endTime = Long.MAX_VALUE;
116  private String filter = null;
117  private int maxVersions = Integer.MAX_VALUE;
118  private int caching = -1;
119  private List<String> labels = new ArrayList<>();
120  private boolean cacheBlocks = true;
121  private int limit = -1;
122
123  /**
124   * Implement lazily-instantiated singleton as per recipe
125   * here: http://literatejava.com/jvm/fastest-threadsafe-singleton-jvm/
126   */
127  private static class JaxbJsonProviderHolder {
128    static final JacksonJaxbJsonProvider INSTANCE = new JacksonJaxbJsonProvider();
129  }
130
131  @XmlRootElement
132  static class FilterModel {
133
134    @XmlRootElement
135    static class ByteArrayComparableModel {
136      @XmlAttribute public String type;
137      @XmlAttribute public String value;
138      @XmlAttribute public String op;
139
140      static enum ComparatorType {
141        BinaryComparator,
142        BinaryPrefixComparator,
143        BitComparator,
144        NullComparator,
145        RegexStringComparator,
146        SubstringComparator
147      }
148
149      public ByteArrayComparableModel() { }
150
151      public ByteArrayComparableModel(
152          ByteArrayComparable comparator) {
153        String typeName = comparator.getClass().getSimpleName();
154        ComparatorType type = ComparatorType.valueOf(typeName);
155        this.type = typeName;
156        switch (type) {
157          case BinaryComparator:
158          case BinaryPrefixComparator:
159            this.value = Bytes.toString(Base64.getEncoder().encode(comparator.getValue()));
160            break;
161          case BitComparator:
162            this.value = Bytes.toString(Base64.getEncoder().encode(comparator.getValue()));
163            this.op = ((BitComparator)comparator).getOperator().toString();
164            break;
165          case NullComparator:
166            break;
167          case RegexStringComparator:
168          case SubstringComparator:
169            this.value = Bytes.toString(comparator.getValue());
170            break;
171          default:
172            throw new RuntimeException("unhandled filter type: " + type);
173        }
174      }
175
176      public ByteArrayComparable build() {
177        ByteArrayComparable comparator;
178        switch (ComparatorType.valueOf(type)) {
179          case BinaryComparator:
180            comparator = new BinaryComparator(Base64.getDecoder().decode(value));
181            break;
182          case BinaryPrefixComparator:
183            comparator = new BinaryPrefixComparator(Base64.getDecoder().decode(value));
184            break;
185          case BitComparator:
186            comparator = new BitComparator(Base64.getDecoder().decode(value),
187                BitComparator.BitwiseOp.valueOf(op));
188            break;
189          case NullComparator:
190            comparator = new NullComparator();
191            break;
192          case RegexStringComparator:
193            comparator = new RegexStringComparator(value);
194            break;
195          case SubstringComparator:
196            comparator = new SubstringComparator(value);
197            break;
198          default:
199            throw new RuntimeException("unhandled comparator type: " + type);
200        }
201        return comparator;
202      }
203
204    }
205
206    // A grab bag of fields, would have been a union if this were C.
207    // These are null by default and will only be serialized if set (non null).
208    @XmlAttribute public String type;
209    @XmlAttribute public String op;
210    @XmlElement ByteArrayComparableModel comparator;
211    @XmlAttribute public String value;
212    @XmlElement public List<FilterModel> filters;
213    @XmlAttribute public Integer limit;
214    @XmlAttribute public Integer offset;
215    @XmlAttribute public String family;
216    @XmlAttribute public String qualifier;
217    @XmlAttribute public Boolean ifMissing;
218    @XmlAttribute public Boolean latestVersion;
219    @XmlAttribute public String minColumn;
220    @XmlAttribute public Boolean minColumnInclusive;
221    @XmlAttribute public String maxColumn;
222    @XmlAttribute public Boolean maxColumnInclusive;
223    @XmlAttribute public Boolean dropDependentColumn;
224    @XmlAttribute public Float chance;
225    @XmlElement public List<String> prefixes;
226    @XmlElement private List<RowRange> ranges;
227    @XmlElement public List<Long> timestamps;
228
229    static enum FilterType {
230      ColumnCountGetFilter,
231      ColumnPaginationFilter,
232      ColumnPrefixFilter,
233      ColumnRangeFilter,
234      DependentColumnFilter,
235      FamilyFilter,
236      FilterList,
237      FirstKeyOnlyFilter,
238      InclusiveStopFilter,
239      KeyOnlyFilter,
240      MultipleColumnPrefixFilter,
241      MultiRowRangeFilter,
242      PageFilter,
243      PrefixFilter,
244      QualifierFilter,
245      RandomRowFilter,
246      RowFilter,
247      SingleColumnValueExcludeFilter,
248      SingleColumnValueFilter,
249      SkipFilter,
250      TimestampsFilter,
251      ValueFilter,
252      WhileMatchFilter
253    }
254
255    public FilterModel() { }
256
257    public FilterModel(Filter filter) {
258      String typeName = filter.getClass().getSimpleName();
259      FilterType type = FilterType.valueOf(typeName);
260      this.type = typeName;
261      switch (type) {
262        case ColumnCountGetFilter:
263          this.limit = ((ColumnCountGetFilter)filter).getLimit();
264          break;
265        case ColumnPaginationFilter:
266          this.limit = ((ColumnPaginationFilter)filter).getLimit();
267          this.offset = ((ColumnPaginationFilter)filter).getOffset();
268          break;
269        case ColumnPrefixFilter:
270          byte[] src = ((ColumnPrefixFilter)filter).getPrefix();
271          this.value = Bytes.toString(Base64.getEncoder().encode(src));
272          break;
273        case ColumnRangeFilter:
274          ColumnRangeFilter crf = (ColumnRangeFilter)filter;
275          this.minColumn = Bytes.toString(Base64.getEncoder().encode(crf.getMinColumn()));
276          this.minColumnInclusive = crf.getMinColumnInclusive();
277          this.maxColumn = Bytes.toString(Base64.getEncoder().encode(crf.getMaxColumn()));
278          this.maxColumnInclusive = crf.getMaxColumnInclusive();
279          break;
280        case DependentColumnFilter: {
281          DependentColumnFilter dcf = (DependentColumnFilter)filter;
282          this.family = Bytes.toString(Base64.getEncoder().encode(dcf.getFamily()));
283          byte[] qualifier = dcf.getQualifier();
284          if (qualifier != null) {
285            this.qualifier = Bytes.toString(Base64.getEncoder().encode(qualifier));
286          }
287          this.op = dcf.getCompareOperator().toString();
288          this.comparator = new ByteArrayComparableModel(dcf.getComparator());
289          this.dropDependentColumn = dcf.dropDependentColumn();
290        } break;
291        case FilterList:
292          this.op = ((FilterList)filter).getOperator().toString();
293          this.filters = new ArrayList<>();
294          for (Filter child: ((FilterList)filter).getFilters()) {
295            this.filters.add(new FilterModel(child));
296          }
297          break;
298        case FirstKeyOnlyFilter:
299        case KeyOnlyFilter:
300          break;
301        case InclusiveStopFilter:
302          this.value = Bytes.toString(Base64.getEncoder().encode(
303              ((InclusiveStopFilter)filter).getStopRowKey()));
304          break;
305        case MultipleColumnPrefixFilter:
306          this.prefixes = new ArrayList<>();
307          for (byte[] prefix: ((MultipleColumnPrefixFilter)filter).getPrefix()) {
308            this.prefixes.add(Bytes.toString(Base64.getEncoder().encode(prefix)));
309          }
310          break;
311        case MultiRowRangeFilter:
312          this.ranges = new ArrayList<>();
313          for(RowRange range : ((MultiRowRangeFilter)filter).getRowRanges()) {
314            this.ranges.add(new RowRange(range.getStartRow(), range.isStartRowInclusive(),
315                range.getStopRow(), range.isStopRowInclusive()));
316          }
317          break;
318        case PageFilter:
319          this.value = Long.toString(((PageFilter)filter).getPageSize());
320          break;
321        case PrefixFilter:
322          this.value = Bytes.toString(Base64.getEncoder().encode(
323              ((PrefixFilter)filter).getPrefix()));
324          break;
325        case FamilyFilter:
326        case QualifierFilter:
327        case RowFilter:
328        case ValueFilter:
329          this.op = ((CompareFilter)filter).getCompareOperator().toString();
330          this.comparator =
331            new ByteArrayComparableModel(
332              ((CompareFilter)filter).getComparator());
333          break;
334        case RandomRowFilter:
335          this.chance = ((RandomRowFilter)filter).getChance();
336          break;
337        case SingleColumnValueExcludeFilter:
338        case SingleColumnValueFilter: {
339          SingleColumnValueFilter scvf = (SingleColumnValueFilter) filter;
340          this.family = Bytes.toString(Base64.getEncoder().encode(scvf.getFamily()));
341          byte[] qualifier = scvf.getQualifier();
342          if (qualifier != null) {
343            this.qualifier = Bytes.toString(Base64.getEncoder().encode(qualifier));
344          }
345          this.op = scvf.getCompareOperator().toString();
346          this.comparator =
347            new ByteArrayComparableModel(scvf.getComparator());
348          if (scvf.getFilterIfMissing()) {
349            this.ifMissing = true;
350          }
351          if (scvf.getLatestVersionOnly()) {
352            this.latestVersion = true;
353          }
354        } break;
355        case SkipFilter:
356          this.filters = new ArrayList<>();
357          this.filters.add(new FilterModel(((SkipFilter)filter).getFilter()));
358          break;
359        case TimestampsFilter:
360          this.timestamps = ((TimestampsFilter)filter).getTimestamps();
361          break;
362        case WhileMatchFilter:
363          this.filters = new ArrayList<>();
364          this.filters.add(
365            new FilterModel(((WhileMatchFilter)filter).getFilter()));
366          break;
367        default:
368          throw new RuntimeException("unhandled filter type " + type);
369      }
370    }
371
372    public Filter build() {
373      Filter filter;
374      switch (FilterType.valueOf(type)) {
375      case ColumnCountGetFilter:
376        filter = new ColumnCountGetFilter(limit);
377        break;
378      case ColumnPaginationFilter:
379        filter = new ColumnPaginationFilter(limit, offset);
380        break;
381      case ColumnPrefixFilter:
382        filter = new ColumnPrefixFilter(Base64.getDecoder().decode(value));
383        break;
384      case ColumnRangeFilter:
385        filter = new ColumnRangeFilter(Base64.getDecoder().decode(minColumn),
386            minColumnInclusive, Base64.getDecoder().decode(maxColumn),
387            maxColumnInclusive);
388        break;
389      case DependentColumnFilter:
390        filter = new DependentColumnFilter(Base64.getDecoder().decode(family),
391            qualifier != null ? Base64.getDecoder().decode(qualifier) : null,
392            dropDependentColumn, CompareOperator.valueOf(op), comparator.build());
393        break;
394      case FamilyFilter:
395        filter = new FamilyFilter(CompareOperator.valueOf(op), comparator.build());
396        break;
397      case FilterList: {
398        List<Filter> list = new ArrayList<>(filters.size());
399        for (FilterModel model: filters) {
400          list.add(model.build());
401        }
402        filter = new FilterList(FilterList.Operator.valueOf(op), list);
403      } break;
404      case FirstKeyOnlyFilter:
405        filter = new FirstKeyOnlyFilter();
406        break;
407      case InclusiveStopFilter:
408        filter = new InclusiveStopFilter(Base64.getDecoder().decode(value));
409        break;
410      case KeyOnlyFilter:
411        filter = new KeyOnlyFilter();
412        break;
413      case MultipleColumnPrefixFilter: {
414        byte[][] values = new byte[prefixes.size()][];
415        for (int i = 0; i < prefixes.size(); i++) {
416          values[i] = Base64.getDecoder().decode(prefixes.get(i));
417        }
418        filter = new MultipleColumnPrefixFilter(values);
419      } break;
420      case MultiRowRangeFilter: {
421        filter = new MultiRowRangeFilter(ranges);
422      } break;
423      case PageFilter:
424        filter = new PageFilter(Long.parseLong(value));
425        break;
426      case PrefixFilter:
427        filter = new PrefixFilter(Base64.getDecoder().decode(value));
428        break;
429      case QualifierFilter:
430        filter = new QualifierFilter(CompareOperator.valueOf(op), comparator.build());
431        break;
432      case RandomRowFilter:
433        filter = new RandomRowFilter(chance);
434        break;
435      case RowFilter:
436        filter = new RowFilter(CompareOperator.valueOf(op), comparator.build());
437        break;
438      case SingleColumnValueFilter:
439        filter = new SingleColumnValueFilter(Base64.getDecoder().decode(family),
440          qualifier != null ? Base64.getDecoder().decode(qualifier) : null,
441        CompareOperator.valueOf(op), comparator.build());
442        if (ifMissing != null) {
443          ((SingleColumnValueFilter)filter).setFilterIfMissing(ifMissing);
444        }
445        if (latestVersion != null) {
446          ((SingleColumnValueFilter)filter).setLatestVersionOnly(latestVersion);
447        }
448        break;
449      case SingleColumnValueExcludeFilter:
450        filter = new SingleColumnValueExcludeFilter(Base64.getDecoder().decode(family),
451          qualifier != null ? Base64.getDecoder().decode(qualifier) : null,
452        CompareOperator.valueOf(op), comparator.build());
453        if (ifMissing != null) {
454          ((SingleColumnValueExcludeFilter)filter).setFilterIfMissing(ifMissing);
455        }
456        if (latestVersion != null) {
457          ((SingleColumnValueExcludeFilter)filter).setLatestVersionOnly(latestVersion);
458        }
459        break;
460      case SkipFilter:
461        filter = new SkipFilter(filters.get(0).build());
462        break;
463      case TimestampsFilter:
464        filter = new TimestampsFilter(timestamps);
465        break;
466      case ValueFilter:
467        filter = new ValueFilter(CompareOperator.valueOf(op), comparator.build());
468        break;
469      case WhileMatchFilter:
470        filter = new WhileMatchFilter(filters.get(0).build());
471        break;
472      default:
473        throw new RuntimeException("unhandled filter type: " + type);
474      }
475      return filter;
476    }
477
478  }
479
480  /**
481   * Get the <code>JacksonJaxbJsonProvider</code> instance;
482   *
483   * @return A <code>JacksonJaxbJsonProvider</code>.
484   */
485  private static JacksonJaxbJsonProvider getJasonProvider() {
486    return JaxbJsonProviderHolder.INSTANCE;
487  }
488
489  /**
490   * @param s the JSON representation of the filter
491   * @return the filter
492   * @throws Exception
493   */
494  public static Filter buildFilter(String s) throws Exception {
495    FilterModel model = getJasonProvider().locateMapper(FilterModel.class,
496        MediaType.APPLICATION_JSON_TYPE).readValue(s, FilterModel.class);
497    return model.build();
498  }
499
500  /**
501   * @param filter the filter
502   * @return the JSON representation of the filter
503   * @throws Exception
504   */
505  public static String stringifyFilter(final Filter filter) throws Exception {
506    return getJasonProvider().locateMapper(FilterModel.class,
507        MediaType.APPLICATION_JSON_TYPE).writeValueAsString(new FilterModel(filter));
508  }
509
510  private static final byte[] COLUMN_DIVIDER = Bytes.toBytes(":");
511
512  /**
513   * @param scan the scan specification
514   * @throws Exception
515   */
516  public static ScannerModel fromScan(Scan scan) throws Exception {
517    ScannerModel model = new ScannerModel();
518    model.setStartRow(scan.getStartRow());
519    model.setEndRow(scan.getStopRow());
520    Map<byte [], NavigableSet<byte []>> families = scan.getFamilyMap();
521    if (families != null) {
522      for (Map.Entry<byte [], NavigableSet<byte []>> entry : families.entrySet()) {
523        if (entry.getValue() != null) {
524          for (byte[] qualifier: entry.getValue()) {
525            model.addColumn(Bytes.add(entry.getKey(), COLUMN_DIVIDER, qualifier));
526          }
527        } else {
528          model.addColumn(entry.getKey());
529        }
530      }
531    }
532    model.setStartTime(scan.getTimeRange().getMin());
533    model.setEndTime(scan.getTimeRange().getMax());
534    int caching = scan.getCaching();
535    if (caching > 0) {
536      model.setCaching(caching);
537    }
538    int batch = scan.getBatch();
539    if (batch > 0) {
540      model.setBatch(batch);
541    }
542    int maxVersions = scan.getMaxVersions();
543    if (maxVersions > 0) {
544      model.setMaxVersions(maxVersions);
545    }
546    if (scan.getLimit() > 0) {
547      model.setLimit(scan.getLimit());
548    }
549    Filter filter = scan.getFilter();
550    if (filter != null) {
551      model.setFilter(stringifyFilter(filter));
552    }
553    // Add the visbility labels if found in the attributes
554    Authorizations authorizations = scan.getAuthorizations();
555    if (authorizations != null) {
556      List<String> labels = authorizations.getLabels();
557      for (String label : labels) {
558        model.addLabel(label);
559      }
560    }
561    return model;
562  }
563
564  /**
565   * Default constructor
566   */
567  public ScannerModel() {}
568
569  /**
570   * Constructor
571   * @param startRow the start key of the row-range
572   * @param endRow the end key of the row-range
573   * @param columns the columns to scan
574   * @param batch the number of values to return in batch
575   * @param caching the number of rows that the scanner will fetch at once
576   * @param endTime the upper bound on timestamps of values of interest
577   * @param maxVersions the maximum number of versions to return
578   * @param filter a filter specification
579   * (values with timestamps later than this are excluded)
580   */
581  public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns,
582      int batch, int caching, long endTime, int maxVersions, String filter) {
583    super();
584    this.startRow = startRow;
585    this.endRow = endRow;
586    this.columns = columns;
587    this.batch = batch;
588    this.caching = caching;
589    this.endTime = endTime;
590    this.maxVersions = maxVersions;
591    this.filter = filter;
592  }
593
594  /**
595   * Constructor
596   * @param startRow the start key of the row-range
597   * @param endRow the end key of the row-range
598   * @param columns the columns to scan
599   * @param batch the number of values to return in batch
600   * @param caching the number of rows that the scanner will fetch at once
601   * @param startTime the lower bound on timestamps of values of interest
602   * (values with timestamps earlier than this are excluded)
603   * @param endTime the upper bound on timestamps of values of interest
604   * (values with timestamps later than this are excluded)
605   * @param filter a filter specification
606   */
607  public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns,
608      int batch, int caching, long startTime, long endTime, String filter) {
609    super();
610    this.startRow = startRow;
611    this.endRow = endRow;
612    this.columns = columns;
613    this.batch = batch;
614    this.caching = caching;
615    this.startTime = startTime;
616    this.endTime = endTime;
617    this.filter = filter;
618  }
619
620  /**
621   * Add a column to the column set
622   * @param column the column name, as &lt;column&gt;(:&lt;qualifier&gt;)?
623   */
624  public void addColumn(byte[] column) {
625    columns.add(column);
626  }
627
628  /**
629   * Add a visibility label to the scan
630   */
631  public void addLabel(String label) {
632    labels.add(label);
633  }
634  /**
635   * @return true if a start row was specified
636   */
637  public boolean hasStartRow() {
638    return !Bytes.equals(startRow, HConstants.EMPTY_START_ROW);
639  }
640
641  /**
642   * @return start row
643   */
644  @XmlAttribute
645  public byte[] getStartRow() {
646    return startRow;
647  }
648
649  /**
650   * @return true if an end row was specified
651   */
652  public boolean hasEndRow() {
653    return !Bytes.equals(endRow, HConstants.EMPTY_END_ROW);
654  }
655
656  /**
657   * @return end row
658   */
659  @XmlAttribute
660  public byte[] getEndRow() {
661    return endRow;
662  }
663
664  /**
665   * @return list of columns of interest in column:qualifier format, or empty for all
666   */
667  @XmlElement(name="column")
668  public List<byte[]> getColumns() {
669    return columns;
670  }
671
672  @XmlElement(name="labels")
673  public List<String> getLabels() {
674    return labels;
675  }
676
677  /**
678   * @return the number of cells to return in batch
679   */
680  @XmlAttribute
681  public int getBatch() {
682    return batch;
683  }
684
685  /**
686   * @return the number of rows that the scanner to fetch at once
687   */
688  @XmlAttribute
689  public int getCaching() {
690    return caching;
691  }
692
693  /**
694   * @return the limit specification
695   */
696  @XmlAttribute
697  public int getLimit() {
698    return limit;
699  }
700
701  /**
702   * @return true if HFile blocks should be cached on the servers for this scan, false otherwise
703   */
704  @XmlAttribute
705  public boolean getCacheBlocks() {
706    return cacheBlocks;
707  }
708
709  /**
710   * @return the lower bound on timestamps of items of interest
711   */
712  @XmlAttribute
713  public long getStartTime() {
714    return startTime;
715  }
716
717  /**
718   * @return the upper bound on timestamps of items of interest
719   */
720  @XmlAttribute
721  public long getEndTime() {
722    return endTime;
723  }
724
725  /**
726   * @return maximum number of versions to return
727   */
728  @XmlAttribute
729  public int getMaxVersions() {
730    return maxVersions;
731  }
732
733  /**
734   * @return the filter specification
735   */
736  @XmlElement
737  public String getFilter() {
738    return filter;
739  }
740
741  /**
742   * @param startRow start row
743   */
744  public void setStartRow(byte[] startRow) {
745    this.startRow = startRow;
746  }
747
748  /**
749   * @param endRow end row
750   */
751  public void setEndRow(byte[] endRow) {
752    this.endRow = endRow;
753  }
754
755  /**
756   * @param columns list of columns of interest in column:qualifier format, or empty for all
757   */
758  public void setColumns(List<byte[]> columns) {
759    this.columns = columns;
760  }
761
762  /**
763   * @param batch the number of cells to return in batch
764   */
765  public void setBatch(int batch) {
766    this.batch = batch;
767  }
768
769  /**
770   * @param caching the number of rows to fetch at once
771   */
772  public void setCaching(int caching) {
773    this.caching = caching;
774  }
775
776  /**
777   * @param value true if HFile blocks should be cached on the servers for this scan, false otherwise
778   */
779  public void setCacheBlocks(boolean value) {
780    this.cacheBlocks = value;
781  }
782
783  /**
784   * @param limit the number of rows can fetch of each scanner at lifetime
785   */
786  public void setLimit(int limit) {
787    this.limit = limit;
788  }
789
790  /**
791   * @param maxVersions maximum number of versions to return
792   */
793  public void setMaxVersions(int maxVersions) {
794    this.maxVersions = maxVersions;
795  }
796
797  /**
798   * @param startTime the lower bound on timestamps of values of interest
799   */
800  public void setStartTime(long startTime) {
801    this.startTime = startTime;
802  }
803
804  /**
805   * @param endTime the upper bound on timestamps of values of interest
806   */
807  public void setEndTime(long endTime) {
808    this.endTime = endTime;
809  }
810
811  /**
812   * @param filter the filter specification
813   */
814  public void setFilter(String filter) {
815    this.filter = filter;
816  }
817
818  @Override
819  public byte[] createProtobufOutput() {
820    Scanner.Builder builder = Scanner.newBuilder();
821    if (!Bytes.equals(startRow, HConstants.EMPTY_START_ROW)) {
822      builder.setStartRow(UnsafeByteOperations.unsafeWrap(startRow));
823    }
824    if (!Bytes.equals(endRow, HConstants.EMPTY_START_ROW)) {
825      builder.setEndRow(UnsafeByteOperations.unsafeWrap(endRow));
826    }
827    for (byte[] column: columns) {
828      builder.addColumns(UnsafeByteOperations.unsafeWrap(column));
829    }
830    if (startTime != 0) {
831      builder.setStartTime(startTime);
832    }
833    if (endTime != 0) {
834      builder.setEndTime(endTime);
835    }
836    builder.setBatch(getBatch());
837    if (caching > 0) {
838      builder.setCaching(caching);
839    }
840    if (limit > 0){
841      builder.setLimit(limit);
842    }
843    builder.setMaxVersions(maxVersions);
844    if (filter != null) {
845      builder.setFilter(filter);
846    }
847    if (labels != null && labels.size() > 0) {
848      for (String label : labels)
849        builder.addLabels(label);
850    }
851    builder.setCacheBlocks(cacheBlocks);
852    return builder.build().toByteArray();
853  }
854
855  @Override
856  public ProtobufMessageHandler getObjectFromMessage(byte[] message)
857      throws IOException {
858    Scanner.Builder builder = Scanner.newBuilder();
859    ProtobufUtil.mergeFrom(builder, message);
860    if (builder.hasStartRow()) {
861      startRow = builder.getStartRow().toByteArray();
862    }
863    if (builder.hasEndRow()) {
864      endRow = builder.getEndRow().toByteArray();
865    }
866    for (ByteString column: builder.getColumnsList()) {
867      addColumn(column.toByteArray());
868    }
869    if (builder.hasBatch()) {
870      batch = builder.getBatch();
871    }
872    if (builder.hasCaching()) {
873      caching = builder.getCaching();
874    }
875    if (builder.hasLimit()) {
876      limit = builder.getLimit();
877    }
878    if (builder.hasStartTime()) {
879      startTime = builder.getStartTime();
880    }
881    if (builder.hasEndTime()) {
882      endTime = builder.getEndTime();
883    }
884    if (builder.hasMaxVersions()) {
885      maxVersions = builder.getMaxVersions();
886    }
887    if (builder.hasFilter()) {
888      filter = builder.getFilter();
889    }
890    if (builder.getLabelsList() != null) {
891      List<String> labels = builder.getLabelsList();
892      for(String label :  labels) {
893        addLabel(label);
894      }
895    }
896    if (builder.hasCacheBlocks()) {
897      this.cacheBlocks = builder.getCacheBlocks();
898    }
899    return this;
900  }
901
902}