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