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