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.Arrays;
025import java.util.Base64;
026import java.util.List;
027import java.util.Map;
028import java.util.NavigableSet;
029import java.util.Objects;
030import javax.xml.bind.annotation.XmlAttribute;
031import javax.xml.bind.annotation.XmlElement;
032import javax.xml.bind.annotation.XmlRootElement;
033import org.apache.hadoop.hbase.CompareOperator;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.client.Scan;
036import org.apache.hadoop.hbase.filter.BinaryComparator;
037import org.apache.hadoop.hbase.filter.BinaryPrefixComparator;
038import org.apache.hadoop.hbase.filter.BitComparator;
039import org.apache.hadoop.hbase.filter.ByteArrayComparable;
040import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
041import org.apache.hadoop.hbase.filter.ColumnPaginationFilter;
042import org.apache.hadoop.hbase.filter.ColumnPrefixFilter;
043import org.apache.hadoop.hbase.filter.ColumnRangeFilter;
044import org.apache.hadoop.hbase.filter.CompareFilter;
045import org.apache.hadoop.hbase.filter.DependentColumnFilter;
046import org.apache.hadoop.hbase.filter.FamilyFilter;
047import org.apache.hadoop.hbase.filter.Filter;
048import org.apache.hadoop.hbase.filter.FilterList;
049import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
050import org.apache.hadoop.hbase.filter.FuzzyRowFilter;
051import org.apache.hadoop.hbase.filter.InclusiveStopFilter;
052import org.apache.hadoop.hbase.filter.KeyOnlyFilter;
053import org.apache.hadoop.hbase.filter.MultiRowRangeFilter;
054import org.apache.hadoop.hbase.filter.MultiRowRangeFilter.RowRange;
055import org.apache.hadoop.hbase.filter.MultipleColumnPrefixFilter;
056import org.apache.hadoop.hbase.filter.NullComparator;
057import org.apache.hadoop.hbase.filter.PageFilter;
058import org.apache.hadoop.hbase.filter.PrefixFilter;
059import org.apache.hadoop.hbase.filter.QualifierFilter;
060import org.apache.hadoop.hbase.filter.RandomRowFilter;
061import org.apache.hadoop.hbase.filter.RegexStringComparator;
062import org.apache.hadoop.hbase.filter.RowFilter;
063import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter;
064import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
065import org.apache.hadoop.hbase.filter.SkipFilter;
066import org.apache.hadoop.hbase.filter.SubstringComparator;
067import org.apache.hadoop.hbase.filter.TimestampsFilter;
068import org.apache.hadoop.hbase.filter.ValueFilter;
069import org.apache.hadoop.hbase.filter.WhileMatchFilter;
070import org.apache.hadoop.hbase.rest.ProtobufMessageHandler;
071import org.apache.hadoop.hbase.rest.RestUtil;
072import org.apache.hadoop.hbase.rest.protobuf.generated.ScannerMessage.Scanner;
073import org.apache.hadoop.hbase.security.visibility.Authorizations;
074import org.apache.hadoop.hbase.util.Bytes;
075import org.apache.hadoop.hbase.util.Pair;
076import org.apache.yetus.audience.InterfaceAudience;
077
078import org.apache.hbase.thirdparty.com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
079import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
080import org.apache.hbase.thirdparty.com.google.protobuf.CodedInputStream;
081import org.apache.hbase.thirdparty.com.google.protobuf.Message;
082import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
083import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
084
085/**
086 * A representation of Scanner parameters.
087 *
088 * <pre>
089 * &lt;complexType name="Scanner"&gt;
090 *   &lt;sequence&gt;
091 *     &lt;element name="column" type="base64Binary" minOccurs="0" maxOccurs="unbounded"/&gt;
092 *     &lt;element name="filter" type="string" minOccurs="0" maxOccurs="1"&gt;&lt;/element&gt;
093 *   &lt;/sequence&gt;
094 *   &lt;attribute name="startRow" type="base64Binary"&gt;&lt;/attribute&gt;
095 *   &lt;attribute name="endRow" type="base64Binary"&gt;&lt;/attribute&gt;
096 *   &lt;attribute name="batch" type="int"&gt;&lt;/attribute&gt;
097 *   &lt;attribute name="caching" type="int"&gt;&lt;/attribute&gt;
098 *   &lt;attribute name="startTime" type="int"&gt;&lt;/attribute&gt;
099 *   &lt;attribute name="endTime" type="int"&gt;&lt;/attribute&gt;
100 *   &lt;attribute name="maxVersions" type="int"&gt;&lt;/attribute&gt;
101 * &lt;/complexType&gt;
102 * </pre>
103 */
104@XmlRootElement(name = "Scanner")
105@JsonInclude(JsonInclude.Include.NON_NULL)
106@InterfaceAudience.Private
107public class ScannerModel implements ProtobufMessageHandler, Serializable {
108
109  private static final long serialVersionUID = 1L;
110
111  private byte[] startRow = HConstants.EMPTY_START_ROW;
112  private byte[] endRow = HConstants.EMPTY_END_ROW;
113  private List<byte[]> columns = new ArrayList<>();
114  private int batch = Integer.MAX_VALUE;
115  private long startTime = 0;
116  private long endTime = Long.MAX_VALUE;
117  private String filter = null;
118  private int maxVersions = Integer.MAX_VALUE;
119  private int caching = -1;
120  private List<String> labels = new ArrayList<>();
121  private boolean cacheBlocks = true;
122  private int limit = -1;
123
124  @JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = IncludeStartRowFilter.class)
125  private boolean includeStartRow = true;
126
127  @JsonInclude(value = JsonInclude.Include.NON_DEFAULT)
128  private boolean includeStopRow = false;
129
130  @XmlAttribute
131  public boolean isIncludeStopRow() {
132    return includeStopRow;
133  }
134
135  public void setIncludeStopRow(boolean includeStopRow) {
136    this.includeStopRow = includeStopRow;
137  }
138
139  @XmlAttribute
140  public boolean isIncludeStartRow() {
141    return includeStartRow;
142  }
143
144  public void setIncludeStartRow(boolean includeStartRow) {
145    this.includeStartRow = includeStartRow;
146  }
147
148  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
149      value = { "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", "HE_EQUALS_NO_HASHCODE",
150        "HE_EQUALS_USE_HASHCODE" },
151      justification = "1.The supplied value from the JSON Value Filter is of Type Boolean, hence supressing the check, 2.hashCode method will not be invoked, hence supressing the check")
152  private static class IncludeStartRowFilter {
153    @Override
154    public boolean equals(Object value) {
155      return Boolean.TRUE.equals(value);
156    }
157  }
158
159  /**
160   * Implement lazily-instantiated singleton as per recipe here:
161   * http://literatejava.com/jvm/fastest-threadsafe-singleton-jvm/
162   */
163  private static class JaxbJsonProviderHolder {
164    static final JacksonJaxbJsonProvider INSTANCE = new JacksonJaxbJsonProvider();
165  }
166
167  @XmlRootElement
168  static class FilterModel {
169
170    @XmlRootElement
171    static class ByteArrayComparableModel {
172      @XmlAttribute
173      public String type;
174      @XmlAttribute
175      public String value;
176      @XmlAttribute
177      public String op;
178
179      static enum ComparatorType {
180        BinaryComparator,
181        BinaryPrefixComparator,
182        BitComparator,
183        NullComparator,
184        RegexStringComparator,
185        SubstringComparator
186      }
187
188      public ByteArrayComparableModel() {
189      }
190
191      public ByteArrayComparableModel(ByteArrayComparable comparator) {
192        String typeName = comparator.getClass().getSimpleName();
193        ComparatorType type = ComparatorType.valueOf(typeName);
194        this.type = typeName;
195        switch (type) {
196          case BinaryComparator:
197          case BinaryPrefixComparator:
198            this.value = Bytes.toString(Base64.getEncoder().encode(comparator.getValue()));
199            break;
200          case BitComparator:
201            this.value = Bytes.toString(Base64.getEncoder().encode(comparator.getValue()));
202            this.op = ((BitComparator) comparator).getOperator().toString();
203            break;
204          case NullComparator:
205            break;
206          case RegexStringComparator:
207          case SubstringComparator:
208            this.value = Bytes.toString(comparator.getValue());
209            break;
210          default:
211            throw new RuntimeException("unhandled filter type: " + type);
212        }
213      }
214
215      public ByteArrayComparable build() {
216        ByteArrayComparable comparator;
217        switch (ComparatorType.valueOf(type)) {
218          case BinaryComparator:
219            comparator = new BinaryComparator(Base64.getDecoder().decode(value));
220            break;
221          case BinaryPrefixComparator:
222            comparator = new BinaryPrefixComparator(Base64.getDecoder().decode(value));
223            break;
224          case BitComparator:
225            comparator = new BitComparator(Base64.getDecoder().decode(value),
226              BitComparator.BitwiseOp.valueOf(op));
227            break;
228          case NullComparator:
229            comparator = new NullComparator();
230            break;
231          case RegexStringComparator:
232            comparator = new RegexStringComparator(value);
233            break;
234          case SubstringComparator:
235            comparator = new SubstringComparator(value);
236            break;
237          default:
238            throw new RuntimeException("unhandled comparator type: " + type);
239        }
240        return comparator;
241      }
242
243    }
244
245    /**
246     * This DTO omits the pseudo-getters in MultiRowRangeFilter.RowRange which break Jackson
247     * deserialization. It also avoids adding those as dummy JSON elements.
248     */
249    static class RowRangeModel {
250
251      protected byte[] startRow;
252
253      protected boolean startRowInclusive = true;
254
255      protected byte[] stopRow;
256
257      protected boolean stopRowInclusive = false;
258
259      public RowRangeModel() {
260      }
261
262      public RowRangeModel(MultiRowRangeFilter.RowRange rr) {
263        this.startRow = rr.getStartRow();
264        this.startRowInclusive = rr.isStartRowInclusive();
265        this.stopRow = rr.getStopRow();
266        this.stopRowInclusive = rr.isStopRowInclusive();
267      }
268
269      public MultiRowRangeFilter.RowRange build() {
270        return new MultiRowRangeFilter.RowRange(startRow, startRowInclusive, stopRow,
271          stopRowInclusive);
272      }
273
274      public byte[] getStartRow() {
275        return startRow;
276      }
277
278      public byte[] getStopRow() {
279        return stopRow;
280      }
281
282      /** Returns if start row is inclusive. */
283      public boolean isStartRowInclusive() {
284        return startRowInclusive;
285      }
286
287      /** Returns if stop row is inclusive. */
288      public boolean isStopRowInclusive() {
289        return stopRowInclusive;
290      }
291
292      @Override
293      public int hashCode() {
294        final int prime = 31;
295        int result = 1;
296        result = prime * result + Arrays.hashCode(startRow);
297        result = prime * result + Arrays.hashCode(stopRow);
298        result = prime * result + Objects.hash(startRowInclusive, stopRowInclusive);
299        return result;
300      }
301
302      @Override
303      public boolean equals(Object obj) {
304        if (this == obj) {
305          return true;
306        }
307        if (!(obj instanceof RowRangeModel)) {
308          return false;
309        }
310        RowRangeModel other = (RowRangeModel) obj;
311        return Arrays.equals(startRow, other.startRow)
312          && startRowInclusive == other.startRowInclusive && Arrays.equals(stopRow, other.stopRow)
313          && stopRowInclusive == other.stopRowInclusive;
314      }
315
316    }
317
318    static class FuzzyKeyModel {
319
320      protected byte[] key;
321
322      protected byte[] mask;
323
324      public FuzzyKeyModel() {
325      }
326
327      public FuzzyKeyModel(Pair<byte[], byte[]> keyWithMask) {
328        this.key = keyWithMask.getFirst();
329        this.mask = keyWithMask.getSecond();
330      }
331
332      public Pair<byte[], byte[]> build() {
333        return new Pair<>(key, mask);
334      }
335
336      public byte[] getKey() {
337        return key;
338      }
339
340      public void setKey(byte[] key) {
341        this.key = key;
342      }
343
344      public byte[] getMask() {
345        return mask;
346      }
347
348      public void setMask(byte[] mask) {
349        this.mask = mask;
350      }
351
352      @Override
353      public int hashCode() {
354        final int prime = 31;
355        int result = 1;
356        result = prime * result + Arrays.hashCode(key);
357        result = prime * result + Arrays.hashCode(mask);
358        return result;
359      }
360
361      @Override
362      public boolean equals(Object obj) {
363        if (this == obj) {
364          return true;
365        }
366        if (!(obj instanceof FuzzyKeyModel)) {
367          return false;
368        }
369        FuzzyKeyModel other = (FuzzyKeyModel) obj;
370        return Arrays.equals(key, other.key) && Arrays.equals(mask, other.mask);
371      }
372
373    }
374
375    // A grab bag of fields, would have been a union if this were C.
376    // These are null by default and will only be serialized if set (non null).
377    @XmlAttribute
378    public String type;
379    @XmlAttribute
380    public String op;
381    @XmlElement
382    ByteArrayComparableModel comparator;
383    @XmlAttribute
384    public String value;
385    @XmlElement
386    public List<FilterModel> filters;
387    @XmlAttribute
388    public Integer limit;
389    @XmlAttribute
390    public Integer offset;
391    @XmlAttribute
392    public String family;
393    @XmlAttribute
394    public String qualifier;
395    @XmlAttribute
396    public Boolean ifMissing;
397    @XmlAttribute
398    public Boolean latestVersion;
399    @XmlAttribute
400    public String minColumn;
401    @XmlAttribute
402    public Boolean minColumnInclusive;
403    @XmlAttribute
404    public String maxColumn;
405    @XmlAttribute
406    public Boolean maxColumnInclusive;
407    @XmlAttribute
408    public Boolean dropDependentColumn;
409    @XmlAttribute
410    public Float chance;
411    @XmlElement
412    public List<String> prefixes;
413    @XmlElement
414    private List<RowRangeModel> ranges;
415    @XmlElement
416    public List<Long> timestamps;
417    @XmlElement
418    private List<FuzzyKeyModel> fuzzyKeys;
419
420    static enum FilterType {
421      ColumnCountGetFilter,
422      ColumnPaginationFilter,
423      ColumnPrefixFilter,
424      ColumnRangeFilter,
425      DependentColumnFilter,
426      FamilyFilter,
427      FilterList,
428      FirstKeyOnlyFilter,
429      InclusiveStopFilter,
430      KeyOnlyFilter,
431      MultipleColumnPrefixFilter,
432      MultiRowRangeFilter,
433      PageFilter,
434      PrefixFilter,
435      QualifierFilter,
436      RandomRowFilter,
437      RowFilter,
438      SingleColumnValueExcludeFilter,
439      SingleColumnValueFilter,
440      SkipFilter,
441      TimestampsFilter,
442      ValueFilter,
443      WhileMatchFilter,
444      FuzzyRowFilter
445    }
446
447    public FilterModel() {
448    }
449
450    public FilterModel(Filter filter) {
451      String typeName = filter.getClass().getSimpleName();
452      FilterType type = FilterType.valueOf(typeName);
453      this.type = typeName;
454      switch (type) {
455        case ColumnCountGetFilter:
456          this.limit = ((ColumnCountGetFilter) filter).getLimit();
457          break;
458        case ColumnPaginationFilter:
459          this.limit = ((ColumnPaginationFilter) filter).getLimit();
460          this.offset = ((ColumnPaginationFilter) filter).getOffset();
461          break;
462        case ColumnPrefixFilter:
463          byte[] src = ((ColumnPrefixFilter) filter).getPrefix();
464          this.value = Bytes.toString(Base64.getEncoder().encode(src));
465          break;
466        case ColumnRangeFilter:
467          ColumnRangeFilter crf = (ColumnRangeFilter) filter;
468          this.minColumn = Bytes.toString(Base64.getEncoder().encode(crf.getMinColumn()));
469          this.minColumnInclusive = crf.getMinColumnInclusive();
470          this.maxColumn = Bytes.toString(Base64.getEncoder().encode(crf.getMaxColumn()));
471          this.maxColumnInclusive = crf.getMaxColumnInclusive();
472          break;
473        case DependentColumnFilter: {
474          DependentColumnFilter dcf = (DependentColumnFilter) filter;
475          this.family = Bytes.toString(Base64.getEncoder().encode(dcf.getFamily()));
476          byte[] qualifier = dcf.getQualifier();
477          if (qualifier != null) {
478            this.qualifier = Bytes.toString(Base64.getEncoder().encode(qualifier));
479          }
480          this.op = dcf.getCompareOperator().toString();
481          this.comparator = new ByteArrayComparableModel(dcf.getComparator());
482          this.dropDependentColumn = dcf.dropDependentColumn();
483        }
484          break;
485        case FilterList:
486          this.op = ((FilterList) filter).getOperator().toString();
487          this.filters = new ArrayList<>();
488          for (Filter child : ((FilterList) filter).getFilters()) {
489            this.filters.add(new FilterModel(child));
490          }
491          break;
492        case FirstKeyOnlyFilter:
493        case KeyOnlyFilter:
494          break;
495        case InclusiveStopFilter:
496          this.value = Bytes
497            .toString(Base64.getEncoder().encode(((InclusiveStopFilter) filter).getStopRowKey()));
498          break;
499        case MultipleColumnPrefixFilter:
500          this.prefixes = new ArrayList<>();
501          for (byte[] prefix : ((MultipleColumnPrefixFilter) filter).getPrefix()) {
502            this.prefixes.add(Bytes.toString(Base64.getEncoder().encode(prefix)));
503          }
504          break;
505        case MultiRowRangeFilter:
506          this.ranges = new ArrayList<>();
507          for (RowRange range : ((MultiRowRangeFilter) filter).getRowRanges()) {
508            this.ranges.add(new RowRangeModel(range));
509          }
510          break;
511        case PageFilter:
512          this.value = Long.toString(((PageFilter) filter).getPageSize());
513          break;
514        case PrefixFilter:
515          this.value =
516            Bytes.toString(Base64.getEncoder().encode(((PrefixFilter) filter).getPrefix()));
517          break;
518        case FamilyFilter:
519        case QualifierFilter:
520        case RowFilter:
521        case ValueFilter:
522          this.op = ((CompareFilter) filter).getCompareOperator().toString();
523          this.comparator = new ByteArrayComparableModel(((CompareFilter) filter).getComparator());
524          break;
525        case RandomRowFilter:
526          this.chance = ((RandomRowFilter) filter).getChance();
527          break;
528        case SingleColumnValueExcludeFilter:
529        case SingleColumnValueFilter: {
530          SingleColumnValueFilter scvf = (SingleColumnValueFilter) filter;
531          this.family = Bytes.toString(Base64.getEncoder().encode(scvf.getFamily()));
532          byte[] qualifier = scvf.getQualifier();
533          if (qualifier != null) {
534            this.qualifier = Bytes.toString(Base64.getEncoder().encode(qualifier));
535          }
536          this.op = scvf.getCompareOperator().toString();
537          this.comparator = new ByteArrayComparableModel(scvf.getComparator());
538          if (scvf.getFilterIfMissing()) {
539            this.ifMissing = true;
540          }
541          if (scvf.getLatestVersionOnly()) {
542            this.latestVersion = true;
543          }
544        }
545          break;
546        case SkipFilter:
547          this.filters = new ArrayList<>();
548          this.filters.add(new FilterModel(((SkipFilter) filter).getFilter()));
549          break;
550        case TimestampsFilter:
551          this.timestamps = ((TimestampsFilter) filter).getTimestamps();
552          break;
553        case WhileMatchFilter:
554          this.filters = new ArrayList<>();
555          this.filters.add(new FilterModel(((WhileMatchFilter) filter).getFilter()));
556          break;
557        case FuzzyRowFilter:
558          this.fuzzyKeys = new ArrayList<>(((FuzzyRowFilter) filter).getFuzzyKeys().size());
559          for (Pair<byte[], byte[]> keyWithMask : ((FuzzyRowFilter) filter).getFuzzyKeys()) {
560            this.fuzzyKeys.add(new FuzzyKeyModel(keyWithMask));
561          }
562          break;
563        default:
564          throw new RuntimeException("unhandled filter type " + type);
565      }
566    }
567
568    public Filter build() {
569      Filter filter;
570      switch (FilterType.valueOf(type)) {
571        case ColumnCountGetFilter:
572          filter = new ColumnCountGetFilter(limit);
573          break;
574        case ColumnPaginationFilter:
575          filter = new ColumnPaginationFilter(limit, offset);
576          break;
577        case ColumnPrefixFilter:
578          filter = new ColumnPrefixFilter(Base64.getDecoder().decode(value));
579          break;
580        case ColumnRangeFilter:
581          filter = new ColumnRangeFilter(Base64.getDecoder().decode(minColumn), minColumnInclusive,
582            Base64.getDecoder().decode(maxColumn), maxColumnInclusive);
583          break;
584        case DependentColumnFilter:
585          filter = new DependentColumnFilter(Base64.getDecoder().decode(family),
586            qualifier != null ? Base64.getDecoder().decode(qualifier) : null, dropDependentColumn,
587            CompareOperator.valueOf(op), comparator.build());
588          break;
589        case FamilyFilter:
590          filter = new FamilyFilter(CompareOperator.valueOf(op), comparator.build());
591          break;
592        case FilterList: {
593          List<Filter> list = new ArrayList<>(filters.size());
594          for (FilterModel model : filters) {
595            list.add(model.build());
596          }
597          filter = new FilterList(FilterList.Operator.valueOf(op), list);
598        }
599          break;
600        case FirstKeyOnlyFilter:
601          filter = new FirstKeyOnlyFilter();
602          break;
603        case InclusiveStopFilter:
604          filter = new InclusiveStopFilter(Base64.getDecoder().decode(value));
605          break;
606        case KeyOnlyFilter:
607          filter = new KeyOnlyFilter();
608          break;
609        case MultipleColumnPrefixFilter: {
610          byte[][] values = new byte[prefixes.size()][];
611          for (int i = 0; i < prefixes.size(); i++) {
612            values[i] = Base64.getDecoder().decode(prefixes.get(i));
613          }
614          filter = new MultipleColumnPrefixFilter(values);
615        }
616          break;
617        case MultiRowRangeFilter: {
618          ArrayList<MultiRowRangeFilter.RowRange> rowRanges = new ArrayList<>(ranges.size());
619          for (RowRangeModel rangeModel : ranges) {
620            rowRanges.add(rangeModel.build());
621          }
622          filter = new MultiRowRangeFilter(rowRanges);
623        }
624          break;
625        case PageFilter:
626          filter = new PageFilter(Long.parseLong(value));
627          break;
628        case PrefixFilter:
629          filter = new PrefixFilter(Base64.getDecoder().decode(value));
630          break;
631        case QualifierFilter:
632          filter = new QualifierFilter(CompareOperator.valueOf(op), comparator.build());
633          break;
634        case RandomRowFilter:
635          filter = new RandomRowFilter(chance);
636          break;
637        case RowFilter:
638          filter = new RowFilter(CompareOperator.valueOf(op), comparator.build());
639          break;
640        case SingleColumnValueFilter:
641          filter = new SingleColumnValueFilter(Base64.getDecoder().decode(family),
642            qualifier != null ? Base64.getDecoder().decode(qualifier) : null,
643            CompareOperator.valueOf(op), comparator.build());
644          if (ifMissing != null) {
645            ((SingleColumnValueFilter) filter).setFilterIfMissing(ifMissing);
646          }
647          if (latestVersion != null) {
648            ((SingleColumnValueFilter) filter).setLatestVersionOnly(latestVersion);
649          }
650          break;
651        case SingleColumnValueExcludeFilter:
652          filter = new SingleColumnValueExcludeFilter(Base64.getDecoder().decode(family),
653            qualifier != null ? Base64.getDecoder().decode(qualifier) : null,
654            CompareOperator.valueOf(op), comparator.build());
655          if (ifMissing != null) {
656            ((SingleColumnValueExcludeFilter) filter).setFilterIfMissing(ifMissing);
657          }
658          if (latestVersion != null) {
659            ((SingleColumnValueExcludeFilter) filter).setLatestVersionOnly(latestVersion);
660          }
661          break;
662        case SkipFilter:
663          filter = new SkipFilter(filters.get(0).build());
664          break;
665        case TimestampsFilter:
666          filter = new TimestampsFilter(timestamps);
667          break;
668        case ValueFilter:
669          filter = new ValueFilter(CompareOperator.valueOf(op), comparator.build());
670          break;
671        case WhileMatchFilter:
672          filter = new WhileMatchFilter(filters.get(0).build());
673          break;
674        case FuzzyRowFilter: {
675          ArrayList<Pair<byte[], byte[]>> fuzzyKeyArgs = new ArrayList<>(fuzzyKeys.size());
676          for (FuzzyKeyModel keyModel : fuzzyKeys) {
677            fuzzyKeyArgs.add(keyModel.build());
678          }
679          filter = new FuzzyRowFilter(fuzzyKeyArgs);
680        }
681          break;
682        default:
683          throw new RuntimeException("unhandled filter type: " + type);
684      }
685      return filter;
686    }
687
688  }
689
690  /**
691   * Get the <code>JacksonJaxbJsonProvider</code> instance;
692   * @return A <code>JacksonJaxbJsonProvider</code>.
693   */
694  private static JacksonJaxbJsonProvider getJasonProvider() {
695    return JaxbJsonProviderHolder.INSTANCE;
696  }
697
698  /**
699   * @param s the JSON representation of the filter
700   * @return the filter
701   */
702  public static Filter buildFilter(String s) throws Exception {
703    FilterModel model =
704      getJasonProvider().locateMapper(FilterModel.class, MediaType.APPLICATION_JSON_TYPE)
705        .readValue(s, FilterModel.class);
706    return model.build();
707  }
708
709  /**
710   * @param filter the filter
711   * @return the JSON representation of the filter
712   */
713  public static String stringifyFilter(final Filter filter) throws Exception {
714    return getJasonProvider().locateMapper(FilterModel.class, MediaType.APPLICATION_JSON_TYPE)
715      .writeValueAsString(new FilterModel(filter));
716  }
717
718  private static final byte[] COLUMN_DIVIDER = Bytes.toBytes(":");
719
720  /**
721   * @param scan the scan specification
722   */
723  public static ScannerModel fromScan(Scan scan) throws Exception {
724    ScannerModel model = new ScannerModel();
725    model.setStartRow(scan.getStartRow());
726    model.setEndRow(scan.getStopRow());
727    Map<byte[], NavigableSet<byte[]>> families = scan.getFamilyMap();
728    if (families != null) {
729      for (Map.Entry<byte[], NavigableSet<byte[]>> entry : families.entrySet()) {
730        if (entry.getValue() != null) {
731          for (byte[] qualifier : entry.getValue()) {
732            model.addColumn(Bytes.add(entry.getKey(), COLUMN_DIVIDER, qualifier));
733          }
734        } else {
735          model.addColumn(entry.getKey());
736        }
737      }
738    }
739    model.setStartTime(scan.getTimeRange().getMin());
740    model.setEndTime(scan.getTimeRange().getMax());
741    int caching = scan.getCaching();
742    if (caching > 0) {
743      model.setCaching(caching);
744    }
745    int batch = scan.getBatch();
746    if (batch > 0) {
747      model.setBatch(batch);
748    }
749    int maxVersions = scan.getMaxVersions();
750    if (maxVersions > 0) {
751      model.setMaxVersions(maxVersions);
752    }
753    if (scan.getLimit() > 0) {
754      model.setLimit(scan.getLimit());
755    }
756    Filter filter = scan.getFilter();
757    if (filter != null) {
758      model.setFilter(stringifyFilter(filter));
759    }
760    // Add the visbility labels if found in the attributes
761    Authorizations authorizations = scan.getAuthorizations();
762    if (authorizations != null) {
763      List<String> labels = authorizations.getLabels();
764      for (String label : labels) {
765        model.addLabel(label);
766      }
767    }
768    model.setIncludeStartRow(scan.includeStartRow());
769    model.setIncludeStopRow(scan.includeStopRow());
770    return model;
771  }
772
773  /**
774   * Default constructor
775   */
776  public ScannerModel() {
777  }
778
779  /**
780   * Constructor
781   * @param startRow    the start key of the row-range
782   * @param endRow      the end key of the row-range
783   * @param columns     the columns to scan
784   * @param batch       the number of values to return in batch
785   * @param caching     the number of rows that the scanner will fetch at once
786   * @param endTime     the upper bound on timestamps of values of interest
787   * @param maxVersions the maximum number of versions to return
788   * @param filter      a filter specification (values with timestamps later than this are excluded)
789   */
790  public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns, int batch, int caching,
791    long endTime, int maxVersions, String filter) {
792    super();
793    this.startRow = startRow;
794    this.endRow = endRow;
795    this.columns = columns;
796    this.batch = batch;
797    this.caching = caching;
798    this.endTime = endTime;
799    this.maxVersions = maxVersions;
800    this.filter = filter;
801  }
802
803  /**
804   * Constructor
805   * @param startRow  the start key of the row-range
806   * @param endRow    the end key of the row-range
807   * @param columns   the columns to scan
808   * @param batch     the number of values to return in batch
809   * @param caching   the number of rows that the scanner will fetch at once
810   * @param startTime the lower bound on timestamps of values of interest (values with timestamps
811   *                  earlier than this are excluded)
812   * @param endTime   the upper bound on timestamps of values of interest (values with timestamps
813   *                  later than this are excluded)
814   * @param filter    a filter specification
815   */
816  public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns, int batch, int caching,
817    long startTime, long endTime, String filter) {
818    super();
819    this.startRow = startRow;
820    this.endRow = endRow;
821    this.columns = columns;
822    this.batch = batch;
823    this.caching = caching;
824    this.startTime = startTime;
825    this.endTime = endTime;
826    this.filter = filter;
827  }
828
829  /**
830   * Add a column to the column set
831   * @param column the column name, as &lt;column&gt;(:&lt;qualifier&gt;)?
832   */
833  public void addColumn(byte[] column) {
834    columns.add(column);
835  }
836
837  /**
838   * Add a visibility label to the scan
839   */
840  public void addLabel(String label) {
841    labels.add(label);
842  }
843
844  /** Returns true if a start row was specified */
845  public boolean hasStartRow() {
846    return !Bytes.equals(startRow, HConstants.EMPTY_START_ROW);
847  }
848
849  /** Returns start row */
850  @XmlAttribute
851  public byte[] getStartRow() {
852    return startRow;
853  }
854
855  /** Returns true if an end row was specified */
856  public boolean hasEndRow() {
857    return !Bytes.equals(endRow, HConstants.EMPTY_END_ROW);
858  }
859
860  /** Returns end row */
861  @XmlAttribute
862  public byte[] getEndRow() {
863    return endRow;
864  }
865
866  /** Returns list of columns of interest in column:qualifier format, or empty for all */
867  @XmlElement(name = "column")
868  public List<byte[]> getColumns() {
869    return columns;
870  }
871
872  @XmlElement(name = "labels")
873  public List<String> getLabels() {
874    return labels;
875  }
876
877  /** Returns the number of cells to return in batch */
878  @XmlAttribute
879  public int getBatch() {
880    return batch;
881  }
882
883  /** Returns the number of rows that the scanner to fetch at once */
884  @XmlAttribute
885  public int getCaching() {
886    return caching;
887  }
888
889  /** Returns the limit specification */
890  @XmlAttribute
891  public int getLimit() {
892    return limit;
893  }
894
895  /** Returns true if HFile blocks should be cached on the servers for this scan, false otherwise */
896  @XmlAttribute
897  public boolean getCacheBlocks() {
898    return cacheBlocks;
899  }
900
901  /** Returns the lower bound on timestamps of items of interest */
902  @XmlAttribute
903  public long getStartTime() {
904    return startTime;
905  }
906
907  /** Returns the upper bound on timestamps of items of interest */
908  @XmlAttribute
909  public long getEndTime() {
910    return endTime;
911  }
912
913  /** Returns maximum number of versions to return */
914  @XmlAttribute
915  public int getMaxVersions() {
916    return maxVersions;
917  }
918
919  /** Returns the filter specification */
920  @XmlElement
921  public String getFilter() {
922    return filter;
923  }
924
925  /**
926   * @param startRow start row
927   */
928  public void setStartRow(byte[] startRow) {
929    this.startRow = startRow;
930  }
931
932  /**
933   * @param endRow end row
934   */
935  public void setEndRow(byte[] endRow) {
936    this.endRow = endRow;
937  }
938
939  /**
940   * @param columns list of columns of interest in column:qualifier format, or empty for all
941   */
942  public void setColumns(List<byte[]> columns) {
943    this.columns = columns;
944  }
945
946  /**
947   * @param batch the number of cells to return in batch
948   */
949  public void setBatch(int batch) {
950    this.batch = batch;
951  }
952
953  /**
954   * @param caching the number of rows to fetch at once
955   */
956  public void setCaching(int caching) {
957    this.caching = caching;
958  }
959
960  /**
961   * @param value true if HFile blocks should be cached on the servers for this scan, false
962   *              otherwise
963   */
964  public void setCacheBlocks(boolean value) {
965    this.cacheBlocks = value;
966  }
967
968  /**
969   * @param limit the number of rows can fetch of each scanner at lifetime
970   */
971  public void setLimit(int limit) {
972    this.limit = limit;
973  }
974
975  /**
976   * @param maxVersions maximum number of versions to return
977   */
978  public void setMaxVersions(int maxVersions) {
979    this.maxVersions = maxVersions;
980  }
981
982  /**
983   * @param startTime the lower bound on timestamps of values of interest
984   */
985  public void setStartTime(long startTime) {
986    this.startTime = startTime;
987  }
988
989  /**
990   * @param endTime the upper bound on timestamps of values of interest
991   */
992  public void setEndTime(long endTime) {
993    this.endTime = endTime;
994  }
995
996  /**
997   * @param filter the filter specification
998   */
999  public void setFilter(String filter) {
1000    this.filter = filter;
1001  }
1002
1003  @Override
1004  public Message messageFromObject() {
1005    Scanner.Builder builder = Scanner.newBuilder();
1006    if (!Bytes.equals(startRow, HConstants.EMPTY_START_ROW)) {
1007      builder.setStartRow(UnsafeByteOperations.unsafeWrap(startRow));
1008    }
1009    if (!Bytes.equals(endRow, HConstants.EMPTY_START_ROW)) {
1010      builder.setEndRow(UnsafeByteOperations.unsafeWrap(endRow));
1011    }
1012    for (byte[] column : columns) {
1013      builder.addColumns(UnsafeByteOperations.unsafeWrap(column));
1014    }
1015    if (startTime != 0) {
1016      builder.setStartTime(startTime);
1017    }
1018    if (endTime != 0) {
1019      builder.setEndTime(endTime);
1020    }
1021    builder.setBatch(getBatch());
1022    if (caching > 0) {
1023      builder.setCaching(caching);
1024    }
1025    if (limit > 0) {
1026      builder.setLimit(limit);
1027    }
1028    builder.setMaxVersions(maxVersions);
1029    if (filter != null) {
1030      builder.setFilter(filter);
1031    }
1032    if (labels != null && labels.size() > 0) {
1033      for (String label : labels)
1034        builder.addLabels(label);
1035    }
1036    builder.setCacheBlocks(cacheBlocks);
1037    builder.setIncludeStartRow(includeStartRow);
1038    builder.setIncludeStopRow(includeStopRow);
1039    return builder.build();
1040  }
1041
1042  @Override
1043  public ProtobufMessageHandler getObjectFromMessage(CodedInputStream cis) throws IOException {
1044    Scanner.Builder builder = Scanner.newBuilder();
1045    RestUtil.mergeFrom(builder, cis);
1046    if (builder.hasStartRow()) {
1047      startRow = builder.getStartRow().toByteArray();
1048    }
1049    if (builder.hasEndRow()) {
1050      endRow = builder.getEndRow().toByteArray();
1051    }
1052    for (ByteString column : builder.getColumnsList()) {
1053      addColumn(column.toByteArray());
1054    }
1055    if (builder.hasBatch()) {
1056      batch = builder.getBatch();
1057    }
1058    if (builder.hasCaching()) {
1059      caching = builder.getCaching();
1060    }
1061    if (builder.hasLimit()) {
1062      limit = builder.getLimit();
1063    }
1064    if (builder.hasStartTime()) {
1065      startTime = builder.getStartTime();
1066    }
1067    if (builder.hasEndTime()) {
1068      endTime = builder.getEndTime();
1069    }
1070    if (builder.hasMaxVersions()) {
1071      maxVersions = builder.getMaxVersions();
1072    }
1073    if (builder.hasFilter()) {
1074      filter = builder.getFilter();
1075    }
1076    if (builder.getLabelsList() != null) {
1077      List<String> labels = builder.getLabelsList();
1078      for (String label : labels) {
1079        addLabel(label);
1080      }
1081    }
1082    if (builder.hasCacheBlocks()) {
1083      this.cacheBlocks = builder.getCacheBlocks();
1084    }
1085    if (builder.hasIncludeStartRow()) {
1086      this.includeStartRow = builder.getIncludeStartRow();
1087    }
1088    if (builder.hasIncludeStopRow()) {
1089      this.includeStopRow = builder.getIncludeStopRow();
1090    }
1091    return this;
1092  }
1093
1094}