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 static org.junit.Assert.assertEquals; 021 022import com.fasterxml.jackson.databind.ObjectMapper; 023import java.io.IOException; 024import java.net.URLEncoder; 025import java.nio.charset.StandardCharsets; 026import java.util.Base64; 027import java.util.Base64.Encoder; 028import java.util.Collection; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.HBaseClassTestRule; 031import org.apache.hadoop.hbase.HBaseCommonTestingUtil; 032import org.apache.hadoop.hbase.HBaseTestingUtil; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.client.Admin; 035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 036import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 037import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 038import org.apache.hadoop.hbase.rest.client.Client; 039import org.apache.hadoop.hbase.rest.client.Cluster; 040import org.apache.hadoop.hbase.rest.client.Response; 041import org.apache.hadoop.hbase.rest.model.CellModel; 042import org.apache.hadoop.hbase.rest.model.CellSetModel; 043import org.apache.hadoop.hbase.rest.model.RowModel; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.testclassification.RestTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.http.Header; 048import org.apache.http.message.BasicHeader; 049import org.junit.AfterClass; 050import org.junit.BeforeClass; 051import org.junit.ClassRule; 052import org.junit.Test; 053import org.junit.experimental.categories.Category; 054import org.junit.runner.RunWith; 055import org.junit.runners.Parameterized; 056 057import org.apache.hbase.thirdparty.com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; 058import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType; 059 060@Category({ RestTests.class, MediumTests.class }) 061@RunWith(Parameterized.class) 062public class TestMultiRowResource { 063 @ClassRule 064 public static final HBaseClassTestRule CLASS_RULE = 065 HBaseClassTestRule.forClass(TestMultiRowResource.class); 066 067 private static final TableName TABLE = TableName.valueOf("TestRowResource"); 068 private static final String CFA = "a"; 069 private static final String CFB = "b"; 070 private static final String COLUMN_1 = CFA + ":1"; 071 private static final String COLUMN_2 = CFB + ":2"; 072 private static final String ROW_1 = "testrow5"; 073 private static final String VALUE_1 = "testvalue5"; 074 private static final String ROW_2 = "testrow6"; 075 private static final String VALUE_2 = "testvalue6"; 076 077 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 078 private static final HBaseRESTTestingUtility REST_TEST_UTIL = new HBaseRESTTestingUtility(); 079 080 private static final Encoder base64UrlEncoder = java.util.Base64.getUrlEncoder(); 081 082 private static Client client; 083 private static Configuration conf; 084 085 private static Header extraHdr = null; 086 private static boolean csrfEnabled = true; 087 088 @Parameterized.Parameters 089 public static Collection<Object[]> data() { 090 return HBaseCommonTestingUtil.BOOLEAN_PARAMETERIZED; 091 } 092 093 public TestMultiRowResource(Boolean csrf) { 094 csrfEnabled = csrf; 095 } 096 097 @BeforeClass 098 public static void setUpBeforeClass() throws Exception { 099 conf = TEST_UTIL.getConfiguration(); 100 conf.setBoolean(RESTServer.REST_CSRF_ENABLED_KEY, csrfEnabled); 101 if (csrfEnabled) { 102 conf.set(RESTServer.REST_CSRF_BROWSER_USERAGENTS_REGEX_KEY, ".*"); 103 } 104 extraHdr = new BasicHeader(RESTServer.REST_CSRF_CUSTOM_HEADER_DEFAULT, ""); 105 TEST_UTIL.startMiniCluster(); 106 REST_TEST_UTIL.startServletContainer(conf); 107 client = new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())); 108 Admin admin = TEST_UTIL.getAdmin(); 109 if (admin.tableExists(TABLE)) { 110 return; 111 } 112 TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TABLE); 113 ColumnFamilyDescriptor columnFamilyDescriptor = 114 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(CFA)).build(); 115 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 116 columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(CFB)).build(); 117 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 118 admin.createTable(tableDescriptorBuilder.build()); 119 } 120 121 @AfterClass 122 public static void tearDownAfterClass() throws Exception { 123 REST_TEST_UTIL.shutdownServletContainer(); 124 TEST_UTIL.shutdownMiniCluster(); 125 } 126 127 @Test 128 public void testMultiCellGetJSON() throws IOException { 129 String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; 130 String row_6_url = "/" + TABLE + "/" + ROW_2 + "/" + COLUMN_2; 131 132 StringBuilder path = new StringBuilder(); 133 path.append("/"); 134 path.append(TABLE); 135 path.append("/multiget/?row="); 136 path.append(ROW_1); 137 path.append("&row="); 138 path.append(ROW_2); 139 140 if (csrfEnabled) { 141 Response response = client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1)); 142 assertEquals(400, response.getCode()); 143 } 144 145 client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); 146 client.post(row_6_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_2), extraHdr); 147 148 Response response = client.get(path.toString(), Constants.MIMETYPE_JSON); 149 assertEquals(200, response.getCode()); 150 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 151 152 client.delete(row_5_url, extraHdr); 153 client.delete(row_6_url, extraHdr); 154 } 155 156 private void checkMultiCellGetJSON(Response response) throws IOException { 157 assertEquals(200, response.getCode()); 158 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 159 160 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 161 MediaType.APPLICATION_JSON_TYPE); 162 CellSetModel cellSet = mapper.readValue(response.getBody(), CellSetModel.class); 163 164 RowModel rowModel = cellSet.getRows().get(0); 165 assertEquals(ROW_1, new String(rowModel.getKey())); 166 assertEquals(1, rowModel.getCells().size()); 167 CellModel cell = rowModel.getCells().get(0); 168 assertEquals(COLUMN_1, new String(cell.getColumn())); 169 assertEquals(VALUE_1, new String(cell.getValue())); 170 171 rowModel = cellSet.getRows().get(1); 172 assertEquals(ROW_2, new String(rowModel.getKey())); 173 assertEquals(1, rowModel.getCells().size()); 174 cell = rowModel.getCells().get(0); 175 assertEquals(COLUMN_2, new String(cell.getColumn())); 176 assertEquals(VALUE_2, new String(cell.getValue())); 177 } 178 179 // See https://issues.apache.org/jira/browse/HBASE-28174 180 @Test 181 public void testMultiCellGetJSONB64() throws IOException { 182 String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; 183 String row_6_url = "/" + TABLE + "/" + ROW_2 + "/" + COLUMN_2; 184 185 if (csrfEnabled) { 186 Response response = client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1)); 187 assertEquals(400, response.getCode()); 188 } 189 190 client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); 191 client.post(row_6_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_2), extraHdr); 192 193 StringBuilder path = new StringBuilder(); 194 Base64.Encoder encoder = Base64.getUrlEncoder(); 195 path.append("/"); 196 path.append(TABLE); 197 path.append("/multiget/?row="); 198 path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8"))); 199 path.append("&row="); 200 path.append(encoder.encodeToString(ROW_2.getBytes("UTF-8"))); 201 path.append("&e=b64"); // Specify encoding via query string 202 203 Response response = client.get(path.toString(), Constants.MIMETYPE_JSON); 204 205 checkMultiCellGetJSON(response); 206 207 path = new StringBuilder(); 208 path.append("/"); 209 path.append(TABLE); 210 path.append("/multiget/?row="); 211 path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8"))); 212 path.append("&row="); 213 path.append(encoder.encodeToString(ROW_2.getBytes("UTF-8"))); 214 215 Header[] headers = new Header[] { new BasicHeader("Accept", Constants.MIMETYPE_JSON), 216 new BasicHeader("Encoding", "b64") // Specify encoding via header 217 }; 218 response = client.get(path.toString(), headers); 219 220 checkMultiCellGetJSON(response); 221 222 client.delete(row_5_url, extraHdr); 223 client.delete(row_6_url, extraHdr); 224 } 225 226 @Test 227 public void testMultiCellGetXML() throws IOException { 228 String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; 229 String row_6_url = "/" + TABLE + "/" + ROW_2 + "/" + COLUMN_2; 230 231 StringBuilder path = new StringBuilder(); 232 path.append("/"); 233 path.append(TABLE); 234 path.append("/multiget/?row="); 235 path.append(ROW_1); 236 path.append("&row="); 237 path.append(ROW_2); 238 239 client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); 240 client.post(row_6_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_2), extraHdr); 241 242 Response response = client.get(path.toString(), Constants.MIMETYPE_XML); 243 assertEquals(200, response.getCode()); 244 assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type")); 245 246 client.delete(row_5_url, extraHdr); 247 client.delete(row_6_url, extraHdr); 248 } 249 250 @Test 251 public void testMultiCellGetWithColsJSON() throws IOException { 252 String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; 253 String row_6_url = "/" + TABLE + "/" + ROW_2 + "/" + COLUMN_2; 254 255 StringBuilder path = new StringBuilder(); 256 path.append("/"); 257 path.append(TABLE); 258 path.append("/multiget"); 259 path.append("/" + COLUMN_1 + "," + CFB); 260 path.append("?row="); 261 path.append(ROW_1); 262 path.append("&row="); 263 path.append(ROW_2); 264 265 client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); 266 client.post(row_6_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_2), extraHdr); 267 268 Response response = client.get(path.toString(), Constants.MIMETYPE_JSON); 269 assertEquals(200, response.getCode()); 270 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 271 MediaType.APPLICATION_JSON_TYPE); 272 CellSetModel cellSet = mapper.readValue(response.getBody(), CellSetModel.class); 273 assertEquals(2, cellSet.getRows().size()); 274 assertEquals(ROW_1, Bytes.toString(cellSet.getRows().get(0).getKey())); 275 assertEquals(VALUE_1, Bytes.toString(cellSet.getRows().get(0).getCells().get(0).getValue())); 276 assertEquals(ROW_2, Bytes.toString(cellSet.getRows().get(1).getKey())); 277 assertEquals(VALUE_2, Bytes.toString(cellSet.getRows().get(1).getCells().get(0).getValue())); 278 279 client.delete(row_5_url, extraHdr); 280 client.delete(row_6_url, extraHdr); 281 } 282 283 @Test 284 public void testMultiCellGetJSONNotFound() throws IOException { 285 String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; 286 287 StringBuilder path = new StringBuilder(); 288 path.append("/"); 289 path.append(TABLE); 290 path.append("/multiget/?row="); 291 path.append(ROW_1); 292 path.append("&row="); 293 path.append(ROW_2); 294 295 client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); 296 Response response = client.get(path.toString(), Constants.MIMETYPE_JSON); 297 assertEquals(200, response.getCode()); 298 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 299 MediaType.APPLICATION_JSON_TYPE); 300 CellSetModel cellSet = (CellSetModel) mapper.readValue(response.getBody(), CellSetModel.class); 301 assertEquals(1, cellSet.getRows().size()); 302 assertEquals(ROW_1, Bytes.toString(cellSet.getRows().get(0).getKey())); 303 assertEquals(VALUE_1, Bytes.toString(cellSet.getRows().get(0).getCells().get(0).getValue())); 304 client.delete(row_5_url, extraHdr); 305 } 306 307 @Test 308 public void testMultiCellGetWithColsInQueryPathJSON() throws IOException { 309 String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; 310 String row_6_url = "/" + TABLE + "/" + ROW_2 + "/" + COLUMN_2; 311 312 StringBuilder path = new StringBuilder(); 313 path.append("/"); 314 path.append(TABLE); 315 path.append("/multiget/?row="); 316 path.append(ROW_1); 317 path.append("/"); 318 path.append(COLUMN_1); 319 path.append("&row="); 320 path.append(ROW_2); 321 path.append("/"); 322 path.append(COLUMN_1); 323 324 client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); 325 client.post(row_6_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_2), extraHdr); 326 327 Response response = client.get(path.toString(), Constants.MIMETYPE_JSON); 328 assertEquals(200, response.getCode()); 329 ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, 330 MediaType.APPLICATION_JSON_TYPE); 331 CellSetModel cellSet = mapper.readValue(response.getBody(), CellSetModel.class); 332 assertEquals(1, cellSet.getRows().size()); 333 assertEquals(ROW_1, Bytes.toString(cellSet.getRows().get(0).getKey())); 334 assertEquals(VALUE_1, Bytes.toString(cellSet.getRows().get(0).getCells().get(0).getValue())); 335 336 client.delete(row_5_url, extraHdr); 337 client.delete(row_6_url, extraHdr); 338 } 339 340 @Test 341 public void testMultiCellGetFilterJSON() throws IOException { 342 String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; 343 String row_6_url = "/" + TABLE + "/" + ROW_2 + "/" + COLUMN_2; 344 345 StringBuilder path = new StringBuilder(); 346 path.append("/"); 347 path.append(TABLE); 348 path.append("/multiget/?row="); 349 path.append(ROW_1); 350 path.append("&row="); 351 path.append(ROW_2); 352 353 if (csrfEnabled) { 354 Response response = client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1)); 355 assertEquals(400, response.getCode()); 356 } 357 358 client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); 359 client.post(row_6_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_2), extraHdr); 360 361 Response response = client.get(path.toString(), Constants.MIMETYPE_JSON); 362 assertEquals(200, response.getCode()); 363 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 364 365 // If the filter is used, then we get the same result 366 String positivePath = path.toString() + ("&" + Constants.FILTER_B64 + "=" + base64UrlEncoder 367 .encodeToString("PrefixFilter('testrow')".getBytes(StandardCharsets.UTF_8.toString()))); 368 response = client.get(positivePath, Constants.MIMETYPE_JSON); 369 checkMultiCellGetJSON(response); 370 371 // Same with non binary clean param 372 positivePath = path.toString() + ("&" + Constants.FILTER + "=" 373 + URLEncoder.encode("PrefixFilter('testrow')", StandardCharsets.UTF_8.name())); 374 response = client.get(positivePath, Constants.MIMETYPE_JSON); 375 checkMultiCellGetJSON(response); 376 377 // This filter doesn't match the found rows 378 String negativePath = path.toString() + ("&" + Constants.FILTER_B64 + "=" + base64UrlEncoder 379 .encodeToString("PrefixFilter('notfound')".getBytes(StandardCharsets.UTF_8.toString()))); 380 response = client.get(negativePath, Constants.MIMETYPE_JSON); 381 assertEquals(404, response.getCode()); 382 383 // Same with non binary clean param 384 negativePath = path.toString() + ("&" + Constants.FILTER + "=" 385 + URLEncoder.encode("PrefixFilter('notfound')", StandardCharsets.UTF_8.name())); 386 response = client.get(negativePath, Constants.MIMETYPE_JSON); 387 assertEquals(404, response.getCode()); 388 389 // Check with binary parameters 390 // positive case 391 positivePath = path.toString() + ("&" + Constants.FILTER_B64 + "=" + base64UrlEncoder 392 .encodeToString(Bytes.toBytesBinary("ColumnRangeFilter ('\\x00', true, '\\xff', true)"))); 393 response = client.get(positivePath, Constants.MIMETYPE_JSON); 394 checkMultiCellGetJSON(response); 395 396 // negative case 397 negativePath = path.toString() + ("&" + Constants.FILTER_B64 + "=" + base64UrlEncoder 398 .encodeToString(Bytes.toBytesBinary("ColumnRangeFilter ('\\x00', true, '1', false)"))); 399 response = client.get(negativePath, Constants.MIMETYPE_JSON); 400 assertEquals(404, response.getCode()); 401 402 client.delete(row_5_url, extraHdr); 403 client.delete(row_6_url, extraHdr); 404 } 405 406}