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