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;
019
020import java.io.IOException;
021import java.util.Base64.Decoder;
022import java.util.List;
023import org.apache.hadoop.hbase.CellUtil;
024import org.apache.hadoop.hbase.TableName;
025import org.apache.hadoop.hbase.client.Scan;
026import org.apache.hadoop.hbase.client.Table;
027import org.apache.hadoop.hbase.filter.Filter;
028import org.apache.hadoop.hbase.filter.FilterList;
029import org.apache.hadoop.hbase.filter.ParseFilter;
030import org.apache.hadoop.hbase.filter.PrefixFilter;
031import org.apache.hadoop.hbase.util.Bytes;
032import org.apache.yetus.audience.InterfaceAudience;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import org.apache.hbase.thirdparty.javax.ws.rs.DefaultValue;
037import org.apache.hbase.thirdparty.javax.ws.rs.Encoded;
038import org.apache.hbase.thirdparty.javax.ws.rs.HeaderParam;
039import org.apache.hbase.thirdparty.javax.ws.rs.Path;
040import org.apache.hbase.thirdparty.javax.ws.rs.PathParam;
041import org.apache.hbase.thirdparty.javax.ws.rs.QueryParam;
042
043@InterfaceAudience.Private
044public class TableResource extends ResourceBase {
045
046  String table;
047  private static final Logger LOG = LoggerFactory.getLogger(TableResource.class);
048
049  private static final Decoder base64Urldecoder = java.util.Base64.getUrlDecoder();
050
051  /**
052   * Constructor
053   */
054  public TableResource(String table) throws IOException {
055    super();
056    this.table = table;
057  }
058
059  /** Returns the table name */
060  String getName() {
061    return table;
062  }
063
064  /** Returns true if the table exists n */
065  boolean exists() throws IOException {
066    return servlet.getAdmin().tableExists(TableName.valueOf(table));
067  }
068
069  @Path("exists")
070  public ExistsResource getExistsResource() throws IOException {
071    return new ExistsResource(this);
072  }
073
074  @Path("regions")
075  public RegionsResource getRegionsResource() throws IOException {
076    return new RegionsResource(this);
077  }
078
079  @Path("scanner")
080  public ScannerResource getScannerResource() throws IOException {
081    return new ScannerResource(this);
082  }
083
084  @Path("schema")
085  public SchemaResource getSchemaResource() throws IOException {
086    return new SchemaResource(this);
087  }
088
089  @Path("{multiget: multiget.*}")
090  public MultiRowResource getMultipleRowResource(final @QueryParam("v") String versions,
091    @PathParam("multiget") String path) throws IOException {
092    return new MultiRowResource(this, versions, path.replace("multiget", "").replace("/", ""));
093  }
094
095  @Path("{rowspec: [^*]+}")
096  public RowResource getRowResource(
097    // We need the @Encoded decorator so Jersey won't urldecode before
098    // the RowSpec constructor has a chance to parse
099    final @PathParam("rowspec") @Encoded String rowspec, final @QueryParam("v") String versions,
100    final @QueryParam("check") String check, final @QueryParam("rr") String returnResult,
101    final @HeaderParam("Encoding") String keyEncodingHeader,
102    final @QueryParam(Constants.KEY_ENCODING_QUERY_PARAM_NAME) String keyEncodingQuery)
103    throws IOException {
104    String keyEncoding = (keyEncodingHeader != null) ? keyEncodingHeader : keyEncodingQuery;
105    return new RowResource(this, rowspec, versions, check, returnResult, keyEncoding);
106  }
107
108  // TODO document
109  @Path("{suffixglobbingspec: .*\\*/.+}")
110  public RowResource getRowResourceWithSuffixGlobbing(
111    // We need the @Encoded decorator so Jersey won't urldecode before
112    // the RowSpec constructor has a chance to parse
113    final @PathParam("suffixglobbingspec") @Encoded String suffixglobbingspec,
114    final @QueryParam("v") String versions, final @QueryParam("check") String check,
115    final @QueryParam("rr") String returnResult,
116    final @HeaderParam("Encoding") String keyEncodingHeader,
117    final @QueryParam(Constants.KEY_ENCODING_QUERY_PARAM_NAME) String keyEncodingQuery)
118    throws IOException {
119    String keyEncoding = (keyEncodingHeader != null) ? keyEncodingHeader : keyEncodingQuery;
120    return new RowResource(this, suffixglobbingspec, versions, check, returnResult, keyEncoding);
121  }
122
123  // TODO document
124  // FIXME handle binary rowkeys (like put and delete does)
125  @Path("{scanspec: .*[*]$}")
126  public TableScanResource getScanResource(final @PathParam("scanspec") String scanSpec,
127    @DefaultValue(Integer.MAX_VALUE + "") @QueryParam(Constants.SCAN_LIMIT) int userRequestedLimit,
128    @DefaultValue("") @QueryParam(Constants.SCAN_START_ROW) String startRow,
129    @DefaultValue("") @QueryParam(Constants.SCAN_END_ROW) String endRow,
130    @QueryParam(Constants.SCAN_COLUMN) List<String> column,
131    @DefaultValue("1") @QueryParam(Constants.SCAN_MAX_VERSIONS) int maxVersions,
132    @DefaultValue("-1") @QueryParam(Constants.SCAN_BATCH_SIZE) int batchSize,
133    @DefaultValue("0") @QueryParam(Constants.SCAN_START_TIME) long startTime,
134    @DefaultValue(Long.MAX_VALUE + "") @QueryParam(Constants.SCAN_END_TIME) long endTime,
135    @DefaultValue("true") @QueryParam(Constants.SCAN_CACHE_BLOCKS) boolean cacheBlocks,
136    @DefaultValue("false") @QueryParam(Constants.SCAN_REVERSED) boolean reversed,
137    @QueryParam(Constants.FILTER) String paramFilter,
138    @QueryParam(Constants.FILTER_B64) @Encoded String paramFilterB64) {
139    try {
140      Filter prefixFilter = null;
141      Scan tableScan = new Scan();
142      if (scanSpec.indexOf('*') > 0) {
143        String prefix = scanSpec.substring(0, scanSpec.indexOf('*'));
144        byte[] prefixBytes = Bytes.toBytes(prefix);
145        prefixFilter = new PrefixFilter(Bytes.toBytes(prefix));
146        if (startRow.isEmpty()) {
147          tableScan.withStartRow(prefixBytes);
148        }
149      }
150      if (LOG.isTraceEnabled()) {
151        LOG.trace("Query parameters  : Table Name = > " + this.table + " Start Row => " + startRow
152          + " End Row => " + endRow + " Columns => " + column + " Start Time => " + startTime
153          + " End Time => " + endTime + " Cache Blocks => " + cacheBlocks + " Max Versions => "
154          + maxVersions + " Batch Size => " + batchSize);
155      }
156      Table hTable = RESTServlet.getInstance().getTable(this.table);
157      tableScan.setBatch(batchSize);
158      tableScan.readVersions(maxVersions);
159      tableScan.setTimeRange(startTime, endTime);
160      if (!startRow.isEmpty()) {
161        tableScan.withStartRow(Bytes.toBytes(startRow));
162      }
163      tableScan.withStopRow(Bytes.toBytes(endRow));
164      for (String col : column) {
165        byte[][] parts = CellUtil.parseColumn(Bytes.toBytes(col.trim()));
166        if (parts.length == 1) {
167          if (LOG.isTraceEnabled()) {
168            LOG.trace("Scan family : " + Bytes.toStringBinary(parts[0]));
169          }
170          tableScan.addFamily(parts[0]);
171        } else if (parts.length == 2) {
172          if (LOG.isTraceEnabled()) {
173            LOG.trace("Scan family and column : " + Bytes.toStringBinary(parts[0]) + "  "
174              + Bytes.toStringBinary(parts[1]));
175          }
176          tableScan.addColumn(parts[0], parts[1]);
177        } else {
178          throw new IllegalArgumentException("Invalid column specifier.");
179        }
180      }
181      FilterList filterList = new FilterList();
182      byte[] filterBytes = null;
183      if (paramFilterB64 != null) {
184        filterBytes = base64Urldecoder.decode(paramFilterB64);
185      } else if (paramFilter != null) {
186        // Not binary clean
187        filterBytes = paramFilter.getBytes();
188      }
189      if (filterBytes != null) {
190        // Note that this is a completely different representation of the filters
191        // than the JSON one used in the /table/scanner endpoint
192        ParseFilter pf = new ParseFilter();
193        Filter parsedParamFilter = pf.parseFilterString(filterBytes);
194        if (parsedParamFilter != null) {
195          filterList.addFilter(parsedParamFilter);
196        }
197      }
198      if (prefixFilter != null) {
199        filterList.addFilter(prefixFilter);
200      }
201      if (filterList.size() > 0) {
202        tableScan.setFilter(filterList);
203      }
204
205      int fetchSize = this.servlet.getConfiguration().getInt(Constants.SCAN_FETCH_SIZE, 10);
206      tableScan.setCaching(fetchSize);
207      tableScan.setReversed(reversed);
208      tableScan.setCacheBlocks(cacheBlocks);
209      return new TableScanResource(hTable.getScanner(tableScan), userRequestedLimit);
210    } catch (IOException exp) {
211      servlet.getMetrics().incrementFailedScanRequests(1);
212      processException(exp);
213      LOG.warn(exp.toString(), exp);
214      return null;
215    }
216  }
217}