/* * Copyright 2010 The Apache Software Foundation * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.rest.model; import java.io.IOException; import java.io.Serializable; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.filter.BinaryComparator; import org.apache.hadoop.hbase.filter.BinaryPrefixComparator; import org.apache.hadoop.hbase.filter.ColumnCountGetFilter; import org.apache.hadoop.hbase.filter.CompareFilter; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.filter.FilterList; import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter; import org.apache.hadoop.hbase.filter.InclusiveStopFilter; import org.apache.hadoop.hbase.filter.PageFilter; import org.apache.hadoop.hbase.filter.PrefixFilter; import org.apache.hadoop.hbase.filter.QualifierFilter; import org.apache.hadoop.hbase.filter.RegexStringComparator; import org.apache.hadoop.hbase.filter.RowFilter; import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; import org.apache.hadoop.hbase.filter.SkipFilter; import org.apache.hadoop.hbase.filter.SubstringComparator; import org.apache.hadoop.hbase.filter.ValueFilter; import org.apache.hadoop.hbase.filter.WhileMatchFilter; import org.apache.hadoop.hbase.filter.WritableByteArrayComparable; import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; import org.apache.hadoop.hbase.rest.ProtobufMessageHandler; import org.apache.hadoop.hbase.rest.protobuf.generated.ScannerMessage.Scanner; import org.apache.hadoop.hbase.util.Base64; import org.apache.hadoop.hbase.util.Bytes; import com.google.protobuf.ByteString; import com.sun.jersey.api.json.JSONConfiguration; import com.sun.jersey.api.json.JSONJAXBContext; import com.sun.jersey.api.json.JSONMarshaller; import com.sun.jersey.api.json.JSONUnmarshaller; /** * A representation of Scanner parameters. * * <pre> * <complexType name="Scanner"> * <sequence> * <element name="column" type="base64Binary" minOccurs="0" maxOccurs="unbounded"/> * </sequence> * <element name="filter" type="string" minOccurs="0" maxOccurs="1"></element> * <attribute name="startRow" type="base64Binary"></attribute> * <attribute name="endRow" type="base64Binary"></attribute> * <attribute name="batch" type="int"></attribute> * <attribute name="startTime" type="int"></attribute> * <attribute name="endTime" type="int"></attribute> * <attribute name="maxVersions" type="int"></attribute> * </complexType> * </pre> */ @XmlRootElement(name="Scanner") public class ScannerModel implements ProtobufMessageHandler, Serializable { private static final long serialVersionUID = 1L; private byte[] startRow = HConstants.EMPTY_START_ROW; private byte[] endRow = HConstants.EMPTY_END_ROW;; private List<byte[]> columns = new ArrayList<byte[]>(); private int batch = Integer.MAX_VALUE; private long startTime = 0; private long endTime = Long.MAX_VALUE; private String filter = null; private int maxVersions = Integer.MAX_VALUE; @XmlRootElement static class FilterModel { @XmlRootElement static class WritableByteArrayComparableModel { @XmlAttribute public String type; @XmlAttribute public String value; static enum ComparatorType { BinaryComparator, BinaryPrefixComparator, RegexStringComparator, SubstringComparator } public WritableByteArrayComparableModel() { } public WritableByteArrayComparableModel( WritableByteArrayComparable comparator) { String typeName = comparator.getClass().getSimpleName(); ComparatorType type = ComparatorType.valueOf(typeName); this.type = typeName; switch (type) { case BinaryComparator: case BinaryPrefixComparator: this.value = Base64.encodeBytes(comparator.getValue()); break; case RegexStringComparator: case SubstringComparator: this.value = Bytes.toString(comparator.getValue()); break; default: throw new RuntimeException("unhandled filter type: " + type); } } public WritableByteArrayComparable build() { WritableByteArrayComparable comparator; switch (ComparatorType.valueOf(type)) { case BinaryComparator: { comparator = new BinaryComparator(Base64.decode(value)); } break; case BinaryPrefixComparator: { comparator = new BinaryPrefixComparator(Base64.decode(value)); } break; case RegexStringComparator: { comparator = new RegexStringComparator(value); } break; case SubstringComparator: { comparator = new SubstringComparator(value); } break; default: { throw new RuntimeException("unhandled comparator type: " + type); } } return comparator; } } // a grab bag of fields, would have been a union if this were C @XmlAttribute public String type = null; @XmlAttribute public String op = null; @XmlElement WritableByteArrayComparableModel comparator = null; @XmlAttribute public String value = null; @XmlElement public List<FilterModel> filters = null; @XmlAttribute public Integer limit = null; @XmlAttribute public String family = null; @XmlAttribute public String qualifier = null; @XmlAttribute public Boolean ifMissing = null; @XmlAttribute public Boolean latestVersion = null; static enum FilterType { ColumnCountGetFilter, FilterList, FirstKeyOnlyFilter, InclusiveStopFilter, PageFilter, PrefixFilter, QualifierFilter, RowFilter, SingleColumnValueFilter, SkipFilter, ValueFilter, WhileMatchFilter } public FilterModel() { } public FilterModel(Filter filter) { String typeName = filter.getClass().getSimpleName(); FilterType type = FilterType.valueOf(typeName); this.type = typeName; switch (type) { case ColumnCountGetFilter: this.limit = ((ColumnCountGetFilter)filter).getLimit(); break; case FilterList: this.op = ((FilterList)filter).getOperator().toString(); this.filters = new ArrayList<FilterModel>(); for (Filter child: ((FilterList)filter).getFilters()) { this.filters.add(new FilterModel(child)); } break; case FirstKeyOnlyFilter: break; case InclusiveStopFilter: this.value = Base64.encodeBytes(((InclusiveStopFilter)filter).getStopRowKey()); break; case PageFilter: this.value = Long.toString(((PageFilter)filter).getPageSize()); break; case PrefixFilter: this.value = Base64.encodeBytes(((PrefixFilter)filter).getPrefix()); break; case QualifierFilter: case RowFilter: case ValueFilter: this.op = ((CompareFilter)filter).getOperator().toString(); this.comparator = new WritableByteArrayComparableModel( ((CompareFilter)filter).getComparator()); break; case SingleColumnValueFilter: { SingleColumnValueFilter scvf = (SingleColumnValueFilter) filter; this.family = Base64.encodeBytes(scvf.getFamily()); byte[] qualifier = scvf.getQualifier(); if (qualifier != null) { this.qualifier = Base64.encodeBytes(qualifier); } this.op = scvf.getOperator().toString(); this.comparator = new WritableByteArrayComparableModel(scvf.getComparator()); if (scvf.getFilterIfMissing()) { this.ifMissing = true; } if (scvf.getLatestVersionOnly()) { this.latestVersion = true; } } break; case SkipFilter: this.filters = new ArrayList<FilterModel>(); this.filters.add(new FilterModel(((SkipFilter)filter).getFilter())); break; case WhileMatchFilter: this.filters = new ArrayList<FilterModel>(); this.filters.add( new FilterModel(((WhileMatchFilter)filter).getFilter())); break; default: throw new RuntimeException("unhandled filter type " + type); } } public Filter build() { Filter filter; switch (FilterType.valueOf(type)) { case ColumnCountGetFilter: { filter = new ColumnCountGetFilter(limit); } break; case FilterList: { List<Filter> list = new ArrayList<Filter>(); for (FilterModel model: filters) { list.add(model.build()); } filter = new FilterList(FilterList.Operator.valueOf(op), list); } break; case FirstKeyOnlyFilter: { filter = new FirstKeyOnlyFilter(); } break; case InclusiveStopFilter: { filter = new InclusiveStopFilter(Base64.decode(value)); } break; case PageFilter: { filter = new PageFilter(Long.valueOf(value)); } break; case PrefixFilter: { filter = new PrefixFilter(Base64.decode(value)); } break; case QualifierFilter: { filter = new QualifierFilter(CompareOp.valueOf(op), comparator.build()); } break; case RowFilter: { filter = new RowFilter(CompareOp.valueOf(op), comparator.build()); } break; case SingleColumnValueFilter: { filter = new SingleColumnValueFilter(Base64.decode(family), qualifier != null ? Base64.decode(qualifier) : null, CompareOp.valueOf(op), comparator.build()); if (ifMissing != null) { ((SingleColumnValueFilter)filter).setFilterIfMissing(ifMissing); } if (latestVersion != null) { ((SingleColumnValueFilter)filter).setLatestVersionOnly(latestVersion); } } break; case SkipFilter: { filter = new SkipFilter(filters.get(0).build()); } break; case ValueFilter: { filter = new ValueFilter(CompareOp.valueOf(op), comparator.build()); } break; case WhileMatchFilter: { filter = new WhileMatchFilter(filters.get(0).build()); } break; default: throw new RuntimeException("unhandled filter type: " + type); } return filter; } } /** * @param s the JSON representation of the filter * @return the filter * @throws Exception */ public static Filter buildFilter(String s) throws Exception { JSONJAXBContext context = new JSONJAXBContext(JSONConfiguration.natural().build(), FilterModel.class); JSONUnmarshaller unmarshaller = context.createJSONUnmarshaller(); FilterModel model = unmarshaller.unmarshalFromJSON(new StringReader(s), FilterModel.class); return model.build(); } /** * @param filter the filter * @return the JSON representation of the filter * @throws Exception */ public static String stringifyFilter(final Filter filter) throws Exception { JSONJAXBContext context = new JSONJAXBContext(JSONConfiguration.natural().build(), FilterModel.class); JSONMarshaller marshaller = context.createJSONMarshaller(); StringWriter writer = new StringWriter(); marshaller.marshallToJSON(new FilterModel(filter), writer); return writer.toString(); } /** * @param scan the scan specification * @throws Exception */ public static ScannerModel fromScan(Scan scan) throws Exception { ScannerModel model = new ScannerModel(); model.setStartRow(scan.getStartRow()); model.setEndRow(scan.getStopRow()); byte[][] families = scan.getFamilies(); if (families != null) { for (byte[] column: families) { model.addColumn(column); } } model.setStartTime(scan.getTimeRange().getMin()); model.setEndTime(scan.getTimeRange().getMax()); int caching = scan.getCaching(); if (caching > 0) { model.setBatch(caching); } int maxVersions = scan.getMaxVersions(); if (maxVersions > 0) { model.setMaxVersions(maxVersions); } Filter filter = scan.getFilter(); if (filter != null) { model.setFilter(stringifyFilter(filter)); } return model; } /** * Default constructor */ public ScannerModel() {} /** * Constructor * @param startRow the start key of the row-range * @param endRow the end key of the row-range * @param columns the columns to scan * @param batch the number of values to return in batch * @param endTime the upper bound on timestamps of values of interest * @param maxVersions the maximum number of versions to return * @param filter a filter specification * (values with timestamps later than this are excluded) */ public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns, int batch, long endTime, int maxVersions, String filter) { super(); this.startRow = startRow; this.endRow = endRow; this.columns = columns; this.batch = batch; this.endTime = endTime; this.maxVersions = maxVersions; this.filter = filter; } /** * Constructor * @param startRow the start key of the row-range * @param endRow the end key of the row-range * @param columns the columns to scan * @param batch the number of values to return in batch * @param startTime the lower bound on timestamps of values of interest * (values with timestamps earlier than this are excluded) * @param endTime the upper bound on timestamps of values of interest * (values with timestamps later than this are excluded) * @param filter a filter specification */ public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns, int batch, long startTime, long endTime, String filter) { super(); this.startRow = startRow; this.endRow = endRow; this.columns = columns; this.batch = batch; this.startTime = startTime; this.endTime = endTime; this.filter = filter; } /** * Add a column to the column set * @param column the column name, as <column>(:<qualifier>)? */ public void addColumn(byte[] column) { columns.add(column); } /** * @return true if a start row was specified */ public boolean hasStartRow() { return !Bytes.equals(startRow, HConstants.EMPTY_START_ROW); } /** * @return start row */ @XmlAttribute public byte[] getStartRow() { return startRow; } /** * @return true if an end row was specified */ public boolean hasEndRow() { return !Bytes.equals(endRow, HConstants.EMPTY_END_ROW); } /** * @return end row */ @XmlAttribute public byte[] getEndRow() { return endRow; } /** * @return list of columns of interest in column:qualifier format, or empty for all */ @XmlElement(name="column") public List<byte[]> getColumns() { return columns; } /** * @return the number of cells to return in batch */ @XmlAttribute public int getBatch() { return batch; } /** * @return the lower bound on timestamps of items of interest */ @XmlAttribute public long getStartTime() { return startTime; } /** * @return the upper bound on timestamps of items of interest */ @XmlAttribute public long getEndTime() { return endTime; } /** * @return maximum number of versions to return */ @XmlAttribute public int getMaxVersions() { return maxVersions; } /** * @return the filter specification */ @XmlElement public String getFilter() { return filter; } /** * @param startRow start row */ public void setStartRow(byte[] startRow) { this.startRow = startRow; } /** * @param endRow end row */ public void setEndRow(byte[] endRow) { this.endRow = endRow; } /** * @param columns list of columns of interest in column:qualifier format, or empty for all */ public void setColumns(List<byte[]> columns) { this.columns = columns; } /** * @param batch the number of cells to return in batch */ public void setBatch(int batch) { this.batch = batch; } /** * @param maxVersions maximum number of versions to return */ public void setMaxVersions(int maxVersions) { this.maxVersions = maxVersions; } /** * @param startTime the lower bound on timestamps of values of interest */ public void setStartTime(long startTime) { this.startTime = startTime; } /** * @param endTime the upper bound on timestamps of values of interest */ public void setEndTime(long endTime) { this.endTime = endTime; } /** * @param filter the filter specification */ public void setFilter(String filter) { this.filter = filter; } @Override public byte[] createProtobufOutput() { Scanner.Builder builder = Scanner.newBuilder(); if (!Bytes.equals(startRow, HConstants.EMPTY_START_ROW)) { builder.setStartRow(ByteString.copyFrom(startRow)); } if (!Bytes.equals(endRow, HConstants.EMPTY_START_ROW)) { builder.setEndRow(ByteString.copyFrom(endRow)); } for (byte[] column: columns) { builder.addColumns(ByteString.copyFrom(column)); } builder.setBatch(batch); if (startTime != 0) { builder.setStartTime(startTime); } if (endTime != 0) { builder.setEndTime(endTime); } builder.setBatch(getBatch()); builder.setMaxVersions(maxVersions); if (filter != null) { builder.setFilter(filter); } return builder.build().toByteArray(); } @Override public ProtobufMessageHandler getObjectFromMessage(byte[] message) throws IOException { Scanner.Builder builder = Scanner.newBuilder(); builder.mergeFrom(message); if (builder.hasStartRow()) { startRow = builder.getStartRow().toByteArray(); } if (builder.hasEndRow()) { endRow = builder.getEndRow().toByteArray(); } for (ByteString column: builder.getColumnsList()) { addColumn(column.toByteArray()); } if (builder.hasBatch()) { batch = builder.getBatch(); } if (builder.hasStartTime()) { startTime = builder.getStartTime(); } if (builder.hasEndTime()) { endTime = builder.getEndTime(); } if (builder.hasMaxVersions()) { maxVersions = builder.getMaxVersions(); } if (builder.hasFilter()) { filter = builder.getFilter(); } return this; } }