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 filterBytes = paramFilter.getBytes(); 187 } 188 if (filterBytes != null) { 189 // Note that this is a completely different representation of the filters 190 // than the JSON one used in the /table/scanner endpoint 191 ParseFilter pf = new ParseFilter(); 192 Filter parsedParamFilter = pf.parseFilterString(filterBytes); 193 if (parsedParamFilter != null) { 194 filterList.addFilter(parsedParamFilter); 195 } 196 } 197 if (prefixFilter != null) { 198 filterList.addFilter(prefixFilter); 199 } 200 if (filterList.size() > 0) { 201 tableScan.setFilter(filterList); 202 } 203 204 int fetchSize = this.servlet.getConfiguration().getInt(Constants.SCAN_FETCH_SIZE, 10); 205 tableScan.setCaching(fetchSize); 206 tableScan.setReversed(reversed); 207 tableScan.setCacheBlocks(cacheBlocks); 208 return new TableScanResource(hTable.getScanner(tableScan), userRequestedLimit); 209 } catch (IOException exp) { 210 servlet.getMetrics().incrementFailedScanRequests(1); 211 processException(exp); 212 LOG.warn(exp.toString(), exp); 213 return null; 214 } 215 } 216}