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 @Path("{suffixglobbingspec: .*\\*/.+}") 109 public RowResource getRowResourceWithSuffixGlobbing( 110 // We need the @Encoded decorator so Jersey won't urldecode before 111 // the RowSpec constructor has a chance to parse 112 final @PathParam("suffixglobbingspec") @Encoded String suffixglobbingspec, 113 final @QueryParam("v") String versions, final @QueryParam("check") String check, 114 final @QueryParam("rr") String returnResult, 115 final @HeaderParam("Encoding") String keyEncodingHeader, 116 final @QueryParam(Constants.KEY_ENCODING_QUERY_PARAM_NAME) String keyEncodingQuery) 117 throws IOException { 118 String keyEncoding = (keyEncodingHeader != null) ? keyEncodingHeader : keyEncodingQuery; 119 return new RowResource(this, suffixglobbingspec, versions, check, returnResult, keyEncoding); 120 } 121 122 // FIXME handle binary rowkeys (like put and delete does) 123 @Path("{scanspec: .*[*]$}") 124 public TableScanResource getScanResource(final @PathParam("scanspec") String scanSpec, 125 @DefaultValue(Integer.MAX_VALUE + "") @QueryParam(Constants.SCAN_LIMIT) int userRequestedLimit, 126 @DefaultValue("") @QueryParam(Constants.SCAN_START_ROW) String startRow, 127 @DefaultValue("") @QueryParam(Constants.SCAN_END_ROW) String endRow, 128 @QueryParam(Constants.SCAN_COLUMN) List<String> column, 129 @DefaultValue("1") @QueryParam(Constants.SCAN_MAX_VERSIONS) int maxVersions, 130 @DefaultValue("-1") @QueryParam(Constants.SCAN_BATCH_SIZE) int batchSize, 131 @DefaultValue("0") @QueryParam(Constants.SCAN_START_TIME) long startTime, 132 @DefaultValue(Long.MAX_VALUE + "") @QueryParam(Constants.SCAN_END_TIME) long endTime, 133 @DefaultValue("true") @QueryParam(Constants.SCAN_CACHE_BLOCKS) boolean cacheBlocks, 134 @DefaultValue("false") @QueryParam(Constants.SCAN_REVERSED) boolean reversed, 135 @QueryParam(Constants.FILTER) String paramFilter, 136 @QueryParam(Constants.FILTER_B64) @Encoded String paramFilterB64, 137 @DefaultValue("true") @QueryParam(Constants.SCAN_INCLUDE_START_ROW) boolean includeStartRow, 138 @DefaultValue("false") @QueryParam(Constants.SCAN_INCLUDE_STOP_ROW) boolean includeStopRow) { 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, includeStartRow); 148 } 149 } 150 LOG.trace( 151 "Query parameters : Table Name = > {} Start Row => {} End Row => {} Columns => {} Start Time => {} End Time => {} Cache Blocks => {} Max Versions => {} Batch Size => {} Reversed => {} LIMIT => {} FILTER => {} FILTER_B64 => {} Include Start Row => {} Include Stop Row => {}", 152 this.table, startRow, endRow, column, startTime, endTime, cacheBlocks, maxVersions, 153 batchSize, reversed, userRequestedLimit, paramFilter, paramFilterB64, includeStartRow, 154 includeStopRow); 155 Table hTable = RESTServlet.getInstance().getTable(this.table); 156 tableScan.setBatch(batchSize); 157 tableScan.readVersions(maxVersions); 158 tableScan.setTimeRange(startTime, endTime); 159 if (!startRow.isEmpty()) { 160 tableScan.withStartRow(Bytes.toBytes(startRow), includeStartRow); 161 } 162 tableScan.withStopRow(Bytes.toBytes(endRow), includeStopRow); 163 for (String col : column) { 164 byte[][] parts = CellUtil.parseColumn(Bytes.toBytes(col.trim())); 165 if (parts.length == 1) { 166 if (LOG.isTraceEnabled()) { 167 LOG.trace("Scan family : " + Bytes.toStringBinary(parts[0])); 168 } 169 tableScan.addFamily(parts[0]); 170 } else if (parts.length == 2) { 171 if (LOG.isTraceEnabled()) { 172 LOG.trace("Scan family and column : " + Bytes.toStringBinary(parts[0]) + " " 173 + Bytes.toStringBinary(parts[1])); 174 } 175 tableScan.addColumn(parts[0], parts[1]); 176 } else { 177 throw new IllegalArgumentException("Invalid column specifier."); 178 } 179 } 180 FilterList filterList = new FilterList(); 181 byte[] filterBytes = null; 182 if (paramFilterB64 != null) { 183 filterBytes = base64Urldecoder.decode(paramFilterB64); 184 } else if (paramFilter != null) { 185 filterBytes = paramFilter.getBytes(); 186 } 187 if (filterBytes != null) { 188 // Note that this is a completely different representation of the filters 189 // than the JSON one used in the /table/scanner endpoint 190 ParseFilter pf = new ParseFilter(); 191 Filter parsedParamFilter = pf.parseFilterString(filterBytes); 192 if (parsedParamFilter != null) { 193 filterList.addFilter(parsedParamFilter); 194 } 195 } 196 if (prefixFilter != null) { 197 filterList.addFilter(prefixFilter); 198 } 199 if (filterList.size() > 0) { 200 tableScan.setFilter(filterList); 201 } 202 203 int fetchSize = this.servlet.getConfiguration().getInt(Constants.SCAN_FETCH_SIZE, 10); 204 tableScan.setCaching(fetchSize); 205 tableScan.setReversed(reversed); 206 tableScan.setCacheBlocks(cacheBlocks); 207 return new TableScanResource(hTable.getScanner(tableScan), userRequestedLimit); 208 } catch (IOException exp) { 209 servlet.getMetrics().incrementFailedScanRequests(1); 210 processException(exp); 211 LOG.warn(exp.toString(), exp); 212 return null; 213 } 214 } 215}