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  /** Returns true if a start row was specified */
653  public boolean hasStartRow() {
654    return !Bytes.equals(startRow, HConstants.EMPTY_START_ROW);
655  }
656
657  /** Returns start row */
658  @XmlAttribute
659  public byte[] getStartRow() {
660    return startRow;
661  }
662
663  /** Returns true if an end row was specified */
664  public boolean hasEndRow() {
665    return !Bytes.equals(endRow, HConstants.EMPTY_END_ROW);
666  }
667
668  /** Returns end row */
669  @XmlAttribute
670  public byte[] getEndRow() {
671    return endRow;
672  }
673
674  /** Returns list of columns of interest in column:qualifier format, or empty for all */
675  @XmlElement(name = "column")
676  public List<byte[]> getColumns() {
677    return columns;
678  }
679
680  @XmlElement(name = "labels")
681  public List<String> getLabels() {
682    return labels;
683  }
684
685  /** Returns the number of cells to return in batch */
686  @XmlAttribute
687  public int getBatch() {
688    return batch;
689  }
690
691  /** Returns the number of rows that the scanner to fetch at once */
692  @XmlAttribute
693  public int getCaching() {
694    return caching;
695  }
696
697  /** Returns the limit specification */
698  @XmlAttribute
699  public int getLimit() {
700    return limit;
701  }
702
703  /** Returns true if HFile blocks should be cached on the servers for this scan, false otherwise */
704  @XmlAttribute
705  public boolean getCacheBlocks() {
706    return cacheBlocks;
707  }
708
709  /** Returns the lower bound on timestamps of items of interest */
710  @XmlAttribute
711  public long getStartTime() {
712    return startTime;
713  }
714
715  /** Returns the upper bound on timestamps of items of interest */
716  @XmlAttribute
717  public long getEndTime() {
718    return endTime;
719  }
720
721  /** Returns maximum number of versions to return */
722  @XmlAttribute
723  public int getMaxVersions() {
724    return maxVersions;
725  }
726
727  /** Returns the filter specification */
728  @XmlElement
729  public String getFilter() {
730    return filter;
731  }
732
733  /**
734   * @param startRow start row
735   */
736  public void setStartRow(byte[] startRow) {
737    this.startRow = startRow;
738  }
739
740  /**
741   * @param endRow end row
742   */
743  public void setEndRow(byte[] endRow) {
744    this.endRow = endRow;
745  }
746
747  /**
748   * @param columns list of columns of interest in column:qualifier format, or empty for all
749   */
750  public void setColumns(List<byte[]> columns) {
751    this.columns = columns;
752  }
753
754  /**
755   * @param batch the number of cells to return in batch
756   */
757  public void setBatch(int batch) {
758    this.batch = batch;
759  }
760
761  /**
762   * @param caching the number of rows to fetch at once
763   */
764  public void setCaching(int caching) {
765    this.caching = caching;
766  }
767
768  /**
769   * @param value true if HFile blocks should be cached on the servers for this scan, false
770   *              otherwise
771   */
772  public void setCacheBlocks(boolean value) {
773    this.cacheBlocks = value;
774  }
775
776  /**
777   * @param limit the number of rows can fetch of each scanner at lifetime
778   */
779  public void setLimit(int limit) {
780    this.limit = limit;
781  }
782
783  /**
784   * @param maxVersions maximum number of versions to return
785   */
786  public void setMaxVersions(int maxVersions) {
787    this.maxVersions = maxVersions;
788  }
789
790  /**
791   * @param startTime the lower bound on timestamps of values of interest
792   */
793  public void setStartTime(long startTime) {
794    this.startTime = startTime;
795  }
796
797  /**
798   * @param endTime the upper bound on timestamps of values of interest
799   */
800  public void setEndTime(long endTime) {
801    this.endTime = endTime;
802  }
803
804  /**
805   * @param filter the filter specification
806   */
807  public void setFilter(String filter) {
808    this.filter = filter;
809  }
810
811  @Override
812  public byte[] createProtobufOutput() {
813    Scanner.Builder builder = Scanner.newBuilder();
814    if (!Bytes.equals(startRow, HConstants.EMPTY_START_ROW)) {
815      builder.setStartRow(UnsafeByteOperations.unsafeWrap(startRow));
816    }
817    if (!Bytes.equals(endRow, HConstants.EMPTY_START_ROW)) {
818      builder.setEndRow(UnsafeByteOperations.unsafeWrap(endRow));
819    }
820    for (byte[] column : columns) {
821      builder.addColumns(UnsafeByteOperations.unsafeWrap(column));
822    }
823    if (startTime != 0) {
824      builder.setStartTime(startTime);
825    }
826    if (endTime != 0) {
827      builder.setEndTime(endTime);
828    }
829    builder.setBatch(getBatch());
830    if (caching > 0) {
831      builder.setCaching(caching);
832    }
833    if (limit > 0) {
834      builder.setLimit(limit);
835    }
836    builder.setMaxVersions(maxVersions);
837    if (filter != null) {
838      builder.setFilter(filter);
839    }
840    if (labels != null && labels.size() > 0) {
841      for (String label : labels)
842        builder.addLabels(label);
843    }
844    builder.setCacheBlocks(cacheBlocks);
845    return builder.build().toByteArray();
846  }
847
848  @Override
849  public ProtobufMessageHandler getObjectFromMessage(byte[] message) throws IOException {
850    Scanner.Builder builder = Scanner.newBuilder();
851    ProtobufUtil.mergeFrom(builder, message);
852    if (builder.hasStartRow()) {
853      startRow = builder.getStartRow().toByteArray();
854    }
855    if (builder.hasEndRow()) {
856      endRow = builder.getEndRow().toByteArray();
857    }
858    for (ByteString column : builder.getColumnsList()) {
859      addColumn(column.toByteArray());
860    }
861    if (builder.hasBatch()) {
862      batch = builder.getBatch();
863    }
864    if (builder.hasCaching()) {
865      caching = builder.getCaching();
866    }
867    if (builder.hasLimit()) {
868      limit = builder.getLimit();
869    }
870    if (builder.hasStartTime()) {
871      startTime = builder.getStartTime();
872    }
873    if (builder.hasEndTime()) {
874      endTime = builder.getEndTime();
875    }
876    if (builder.hasMaxVersions()) {
877      maxVersions = builder.getMaxVersions();
878    }
879    if (builder.hasFilter()) {
880      filter = builder.getFilter();
881    }
882    if (builder.getLabelsList() != null) {
883      List<String> labels = builder.getLabelsList();
884      for (String label : labels) {
885        addLabel(label);
886      }
887    }
888    if (builder.hasCacheBlocks()) {
889      this.cacheBlocks = builder.getCacheBlocks();
890    }
891    return this;
892  }
893
894}