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