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.filter;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Objects;
023import org.apache.hadoop.hbase.Cell;
024import org.apache.hadoop.hbase.exceptions.DeserializationException;
025import org.apache.yetus.audience.InterfaceAudience;
026
027import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
028import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
029
030import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos;
031
032/**
033 * Implementation of Filter interface that limits results to a specific page size. It terminates
034 * scanning once the number of filter-passed rows is > the given page size.
035 * <p>
036 * Note that this filter cannot guarantee that the number of results returned to a client are &lt;=
037 * page size. This is because the filter is applied separately on different region servers. It does
038 * however optimize the scan of individual HRegions by making sure that the page size is never
039 * exceeded locally.
040 */
041@InterfaceAudience.Public
042public class PageFilter extends FilterBase {
043  private long pageSize = Long.MAX_VALUE;
044  private int rowsAccepted = 0;
045
046  /**
047   * Constructor that takes a maximum page size.
048   * @param pageSize Maximum result size.
049   */
050  public PageFilter(final long pageSize) {
051    Preconditions.checkArgument(pageSize >= 0, "must be positive %s", pageSize);
052    this.pageSize = pageSize;
053  }
054
055  public long getPageSize() {
056    return pageSize;
057  }
058
059  @Override
060  public boolean filterRowKey(Cell cell) throws IOException {
061    // Impl in FilterBase might do unnecessary copy for Off heap backed Cells.
062    if (filterAllRemaining()) return true;
063    return false;
064  }
065
066  @Override
067  public ReturnCode filterCell(final Cell ignored) throws IOException {
068    return ReturnCode.INCLUDE;
069  }
070
071  @Override
072  public boolean filterAllRemaining() {
073    return this.rowsAccepted >= this.pageSize;
074  }
075
076  @Override
077  public boolean filterRow() {
078    this.rowsAccepted++;
079    return this.rowsAccepted > this.pageSize;
080  }
081
082  @Override
083  public boolean hasFilterRow() {
084    return true;
085  }
086
087  public static Filter createFilterFromArguments(ArrayList<byte[]> filterArguments) {
088    Preconditions.checkArgument(filterArguments.size() == 1, "Expected 1 but got: %s",
089      filterArguments.size());
090    long pageSize = ParseFilter.convertByteArrayToLong(filterArguments.get(0));
091    return new PageFilter(pageSize);
092  }
093
094  /** Returns The filter serialized using pb */
095  @Override
096  public byte[] toByteArray() {
097    FilterProtos.PageFilter.Builder builder = FilterProtos.PageFilter.newBuilder();
098    builder.setPageSize(this.pageSize);
099    return builder.build().toByteArray();
100  }
101
102  /**
103   * Parse a serialized representation of {@link PageFilter}
104   * @param pbBytes A pb serialized {@link PageFilter} instance
105   * @return An instance of {@link PageFilter} made from <code>bytes</code>
106   * @throws DeserializationException if an error occurred
107   * @see #toByteArray
108   */
109  public static PageFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
110    FilterProtos.PageFilter proto;
111    try {
112      proto = FilterProtos.PageFilter.parseFrom(pbBytes);
113    } catch (InvalidProtocolBufferException e) {
114      throw new DeserializationException(e);
115    }
116    return new PageFilter(proto.getPageSize());
117  }
118
119  /**
120   * Returns true if and only if the fields of the filter that are serialized are equal to the
121   * corresponding fields in other. Used for testing.
122   */
123  @Override
124  boolean areSerializedFieldsEqual(Filter o) {
125    if (o == this) {
126      return true;
127    }
128    if (!(o instanceof PageFilter)) {
129      return false;
130    }
131
132    PageFilter other = (PageFilter) o;
133    return this.getPageSize() == other.getPageSize();
134  }
135
136  @Override
137  public String toString() {
138    return this.getClass().getSimpleName() + " " + this.pageSize;
139  }
140
141  @Override
142  public boolean equals(Object obj) {
143    return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
144  }
145
146  @Override
147  public int hashCode() {
148    return Objects.hash(this.pageSize);
149  }
150}