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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022
023import java.io.ByteArrayInputStream;
024import java.io.IOException;
025import java.io.StringWriter;
026import java.net.URLEncoder;
027import java.util.Base64;
028import java.util.HashMap;
029import java.util.List;
030import javax.xml.bind.JAXBException;
031import org.apache.hadoop.hbase.CompatibilityFactory;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.rest.client.Response;
034import org.apache.hadoop.hbase.rest.model.CellModel;
035import org.apache.hadoop.hbase.rest.model.CellSetModel;
036import org.apache.hadoop.hbase.rest.model.RowModel;
037import org.apache.hadoop.hbase.security.UserProvider;
038import org.apache.hadoop.hbase.test.MetricsAssertHelper;
039import org.apache.hadoop.hbase.testclassification.LargeTests;
040import org.apache.hadoop.hbase.testclassification.RestTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.apache.http.Header;
043import org.apache.http.message.BasicHeader;
044import org.junit.jupiter.api.Tag;
045import org.junit.jupiter.api.Test;
046
047@Tag(RestTests.TAG)
048@Tag(LargeTests.TAG)
049public class TestGetAndPutResource extends RowResourceBase {
050
051  private static final MetricsAssertHelper METRICS_ASSERT =
052    CompatibilityFactory.getInstance(MetricsAssertHelper.class);
053
054  @Test
055  public void testForbidden() throws IOException, JAXBException {
056    conf.set("hbase.rest.readonly", "true");
057
058    Response response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
059    assertEquals(403, response.getCode());
060    response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
061    assertEquals(403, response.getCode());
062    response = checkAndPutValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1, VALUE_2);
063    assertEquals(403, response.getCode());
064    response = checkAndPutValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1, VALUE_2);
065    assertEquals(403, response.getCode());
066    response = deleteValue(TABLE, ROW_1, COLUMN_1);
067    assertEquals(403, response.getCode());
068    response = checkAndDeletePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
069    assertEquals(403, response.getCode());
070    response = deleteRow(TABLE, ROW_1);
071    assertEquals(403, response.getCode());
072
073    conf.set("hbase.rest.readonly", "false");
074
075    response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
076    assertEquals(200, response.getCode());
077    response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
078    assertEquals(200, response.getCode());
079    response = checkAndPutValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1, VALUE_2);
080    assertEquals(200, response.getCode());
081    response = checkAndPutValuePB(TABLE, ROW_1, COLUMN_1, VALUE_2, VALUE_3);
082    assertEquals(200, response.getCode());
083    response = deleteValue(TABLE, ROW_1, COLUMN_1);
084    assertEquals(200, response.getCode());
085    response = deleteRow(TABLE, ROW_1);
086    assertEquals(200, response.getCode());
087  }
088
089  @Test
090  public void testSingleCellGetPutXML() throws IOException, JAXBException {
091    Response response = getValueXML(TABLE, ROW_1, COLUMN_1);
092    assertEquals(404, response.getCode());
093
094    response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
095    assertEquals(200, response.getCode());
096    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
097    response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
098    assertEquals(200, response.getCode());
099    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
100    response = checkAndPutValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2, VALUE_3);
101    assertEquals(200, response.getCode());
102    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_3);
103    response = checkAndDeleteXML(TABLE, ROW_1, COLUMN_1, VALUE_3);
104    assertEquals(200, response.getCode());
105
106    response = deleteRow(TABLE, ROW_1);
107    assertEquals(200, response.getCode());
108  }
109
110  @Test
111  public void testSingleCellGetPutPB() throws IOException, JAXBException {
112    Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
113    assertEquals(404, response.getCode());
114
115    response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
116    assertEquals(200, response.getCode());
117    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
118    response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
119    assertEquals(200, response.getCode());
120    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_2);
121
122    response = checkAndPutValuePB(TABLE, ROW_1, COLUMN_1, VALUE_2, VALUE_3);
123    assertEquals(200, response.getCode());
124    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_3);
125    response = checkAndPutValueXML(TABLE, ROW_1, COLUMN_1, VALUE_3, VALUE_4);
126    assertEquals(200, response.getCode());
127    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_4);
128
129    response = deleteRow(TABLE, ROW_1);
130    assertEquals(200, response.getCode());
131  }
132
133  @Test
134  public void testMultipleCellCheckPutPB() throws IOException {
135    Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
136    assertEquals(404, response.getCode());
137
138    // Add 2 Columns to setup the test
139    response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
140    assertEquals(200, response.getCode());
141    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
142
143    response = putValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
144    assertEquals(200, response.getCode());
145    checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
146
147    HashMap<String, String> otherCells = new HashMap<>();
148    otherCells.put(COLUMN_2, VALUE_3);
149
150    // On Success update both the cells
151    response = checkAndPutValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1, VALUE_3, otherCells);
152    assertEquals(200, response.getCode());
153    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_3);
154    checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_3);
155
156    // On Failure, we dont update any cells
157    response = checkAndPutValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1, VALUE_4, otherCells);
158    assertEquals(304, response.getCode());
159    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_3);
160    checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_3);
161
162    response = deleteRow(TABLE, ROW_1);
163    assertEquals(200, response.getCode());
164  }
165
166  @Test
167  public void testMultipleCellCheckPutXML() throws IOException, JAXBException {
168    Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
169    assertEquals(404, response.getCode());
170
171    // Add 2 Columns to setup the test
172    response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
173    assertEquals(200, response.getCode());
174    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
175
176    response = putValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
177    assertEquals(200, response.getCode());
178    checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
179
180    HashMap<String, String> otherCells = new HashMap<>();
181    otherCells.put(COLUMN_2, VALUE_3);
182
183    // On Success update both the cells
184    response = checkAndPutValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1, VALUE_3, otherCells);
185    assertEquals(200, response.getCode());
186    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_3);
187    checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_3);
188
189    // On Failure, we dont update any cells
190    response = checkAndPutValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1, VALUE_4, otherCells);
191    assertEquals(304, response.getCode());
192    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_3);
193    checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_3);
194
195    response = deleteRow(TABLE, ROW_1);
196    assertEquals(200, response.getCode());
197  }
198
199  @Test
200  public void testMultipleCellCheckDeletePB() throws IOException {
201    Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
202    assertEquals(404, response.getCode());
203
204    // Add 3 Columns to setup the test
205    response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
206    assertEquals(200, response.getCode());
207    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
208
209    response = putValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
210    assertEquals(200, response.getCode());
211    checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
212
213    response = putValuePB(TABLE, ROW_1, COLUMN_3, VALUE_3);
214    assertEquals(200, response.getCode());
215    checkValuePB(TABLE, ROW_1, COLUMN_3, VALUE_3);
216
217    // Deletes the following columns based on Column1 check
218    HashMap<String, String> cellsToDelete = new HashMap<>();
219    cellsToDelete.put(COLUMN_2, VALUE_2); // Value does not matter
220    cellsToDelete.put(COLUMN_3, VALUE_3); // Value does not matter
221
222    // On Success update both the cells
223    response = checkAndDeletePB(TABLE, ROW_1, COLUMN_1, VALUE_1, cellsToDelete);
224    assertEquals(200, response.getCode());
225
226    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
227
228    response = getValuePB(TABLE, ROW_1, COLUMN_2);
229    assertEquals(404, response.getCode());
230
231    response = getValuePB(TABLE, ROW_1, COLUMN_3);
232    assertEquals(404, response.getCode());
233
234    response = putValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
235    assertEquals(200, response.getCode());
236    checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
237
238    response = putValuePB(TABLE, ROW_1, COLUMN_3, VALUE_3);
239    assertEquals(200, response.getCode());
240    checkValuePB(TABLE, ROW_1, COLUMN_3, VALUE_3);
241
242    // On Failure, we dont update any cells
243    response = checkAndDeletePB(TABLE, ROW_1, COLUMN_1, VALUE_3, cellsToDelete);
244    assertEquals(304, response.getCode());
245    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
246    checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
247    checkValuePB(TABLE, ROW_1, COLUMN_3, VALUE_3);
248
249    response = deleteRow(TABLE, ROW_1);
250    assertEquals(200, response.getCode());
251  }
252
253  @Test
254  public void testSingleCellGetPutBinary() throws IOException {
255    final String path = "/" + TABLE + "/" + ROW_3 + "/" + COLUMN_1;
256    final byte[] body = Bytes.toBytes(VALUE_3);
257    Response response = client.put(path, Constants.MIMETYPE_BINARY, body);
258    assertEquals(200, response.getCode());
259    Thread.yield();
260
261    response = client.get(path, Constants.MIMETYPE_BINARY);
262    assertEquals(200, response.getCode());
263    assertEquals(Constants.MIMETYPE_BINARY, response.getHeader("content-type"));
264    assertTrue(Bytes.equals(response.getBody(), body));
265    boolean foundTimestampHeader = false;
266    for (Header header : response.getHeaders()) {
267      if (header.getName().equals("X-Timestamp")) {
268        foundTimestampHeader = true;
269        break;
270      }
271    }
272    assertTrue(foundTimestampHeader);
273
274    response = deleteRow(TABLE, ROW_3);
275    assertEquals(200, response.getCode());
276  }
277
278  @Test
279  public void testSingleCellGetJSON() throws IOException {
280    final String path = "/" + TABLE + "/" + ROW_4 + "/" + COLUMN_1;
281    Response response = client.put(path, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_4));
282    assertEquals(200, response.getCode());
283    Thread.yield();
284    response = client.get(path, Constants.MIMETYPE_JSON);
285    assertEquals(200, response.getCode());
286    assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
287    response = deleteRow(TABLE, ROW_4);
288    assertEquals(200, response.getCode());
289  }
290
291  @Test
292  public void testLatestCellGetJSON() throws IOException {
293    final String path = "/" + TABLE + "/" + ROW_4 + "/" + COLUMN_1;
294    CellSetModel cellSetModel = new CellSetModel();
295    RowModel rowModel = new RowModel(ROW_4);
296    CellModel cellOne = new CellModel(Bytes.toBytes(COLUMN_1), 1L, Bytes.toBytes(VALUE_1));
297    CellModel cellTwo = new CellModel(Bytes.toBytes(COLUMN_1), 2L, Bytes.toBytes(VALUE_2));
298    rowModel.addCell(cellOne);
299    rowModel.addCell(cellTwo);
300    cellSetModel.addRow(rowModel);
301    String jsonString = jsonMapper.writeValueAsString(cellSetModel);
302    Response response = client.put(path, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString));
303    assertEquals(200, response.getCode());
304    Thread.yield();
305    response = client.get(path, Constants.MIMETYPE_JSON);
306    assertEquals(200, response.getCode());
307    assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
308    CellSetModel cellSet = jsonMapper.readValue(response.getBody(), CellSetModel.class);
309    assertEquals(1, cellSet.getRows().size());
310    assertEquals(1, cellSet.getRows().get(0).getCells().size());
311    CellModel cell = cellSet.getRows().get(0).getCells().get(0);
312    assertEquals(VALUE_2, Bytes.toString(cell.getValue()));
313    assertEquals(2L, cell.getTimestamp());
314    response = deleteRow(TABLE, ROW_4);
315    assertEquals(200, response.getCode());
316  }
317
318  @Test
319  public void testURLEncodedKey() throws IOException, JAXBException {
320    // Requires UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR
321    // Otherwise fails with "400: Ambiguous URI path separator"
322    // In this test, request url resolves to "/TestRowResource/http%3A%2F%2Fexample.com%2Ffoo/a:1"
323    // and is considered ambiguous by Jetty 12.
324    // Basically we are having a URL encoded string as row key here!
325    String urlKey = "http://example.com/foo";
326    StringBuilder path = new StringBuilder();
327    path.append('/');
328    path.append(TABLE);
329    path.append('/');
330    path.append(URLEncoder.encode(urlKey, HConstants.UTF8_ENCODING));
331    path.append('/');
332    path.append(COLUMN_1);
333    Response response;
334    response = putValueXML(path.toString(), TABLE, urlKey, COLUMN_1, VALUE_1);
335    assertEquals(200, response.getCode());
336    checkValueXML(path.toString(), TABLE, urlKey, COLUMN_1, VALUE_1);
337  }
338
339  private void setupValue1() throws IOException, JAXBException {
340    StringBuilder path = new StringBuilder();
341    path.append('/');
342    path.append(TABLE);
343    path.append('/');
344    path.append(ROW_1);
345    path.append('/');
346    path.append(COLUMN_1);
347    Response response = putValueXML(path.toString(), TABLE, ROW_1, COLUMN_1, VALUE_1);
348    assertEquals(200, response.getCode());
349  }
350
351  private void checkValue1(Response getResponse) throws JAXBException {
352    assertEquals(Constants.MIMETYPE_XML, getResponse.getHeader("content-type"));
353
354    CellSetModel cellSet =
355      (CellSetModel) xmlUnmarshaller.unmarshal(new ByteArrayInputStream(getResponse.getBody()));
356    assertEquals(1, cellSet.getRows().size());
357    RowModel rowModel = cellSet.getRows().get(0);
358    assertEquals(ROW_1, new String(rowModel.getKey()));
359    assertEquals(1, rowModel.getCells().size());
360    CellModel cell = rowModel.getCells().get(0);
361    assertEquals(COLUMN_1, new String(cell.getColumn()));
362    assertEquals(VALUE_1, new String(cell.getValue()));
363  }
364
365  // See https://issues.apache.org/jira/browse/HBASE-28174
366  @Test
367  public void testUrlB64EncodedKeyQueryParam() throws IOException, JAXBException {
368    setupValue1();
369
370    StringBuilder path = new StringBuilder();
371    Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
372    path.append('/');
373    path.append(TABLE);
374    path.append('/');
375    path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8")));
376    path.append('/');
377    path.append(encoder.encodeToString(COLUMN_1.getBytes("UTF-8")));
378    path.append("?e=b64");
379    Response response = getValueXML(path.toString());
380    assertEquals(200, response.getCode());
381
382    checkValue1(response);
383  }
384
385  // See https://issues.apache.org/jira/browse/HBASE-28174
386  @Test
387  public void testUrlB64EncodedKeyHeader() throws IOException, JAXBException {
388    setupValue1();
389
390    StringBuilder path = new StringBuilder();
391    Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
392    path.append('/');
393    path.append(TABLE);
394    path.append('/');
395    path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8")));
396    path.append('/');
397    path.append(encoder.encodeToString(COLUMN_1.getBytes("UTF-8")));
398    Response response =
399      getValueXML(path.toString(), new Header[] { new BasicHeader("Encoding", "b64") });
400    assertEquals(200, response.getCode());
401
402    checkValue1(response);
403  }
404
405  @Test
406  public void testNoSuchCF() throws IOException {
407    final String goodPath = "/" + TABLE + "/" + ROW_1 + "/" + CFA + ":";
408    final String badPath = "/" + TABLE + "/" + ROW_1 + "/" + "BAD";
409    Response response = client.post(goodPath, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1));
410    assertEquals(200, response.getCode());
411    assertEquals(200, client.get(goodPath, Constants.MIMETYPE_BINARY).getCode());
412    assertEquals(404, client.get(badPath, Constants.MIMETYPE_BINARY).getCode());
413    assertEquals(200, client.get(goodPath, Constants.MIMETYPE_BINARY).getCode());
414  }
415
416  @Test
417  public void testMultiCellGetPutXML() throws IOException, JAXBException {
418    String path = "/" + TABLE + "/fakerow"; // deliberate nonexistent row
419
420    CellSetModel cellSetModel = new CellSetModel();
421    RowModel rowModel = new RowModel(ROW_1);
422    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_1)));
423    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_2)));
424    cellSetModel.addRow(rowModel);
425    rowModel = new RowModel(ROW_2);
426    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_3)));
427    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_4)));
428    cellSetModel.addRow(rowModel);
429    StringWriter writer = new StringWriter();
430    xmlMarshaller.marshal(cellSetModel, writer);
431    Response response = client.put(path, Constants.MIMETYPE_XML, Bytes.toBytes(writer.toString()));
432    Thread.yield();
433
434    // make sure the fake row was not actually created
435    response = client.get(path, Constants.MIMETYPE_XML);
436    assertEquals(404, response.getCode());
437
438    // check that all of the values were created
439    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
440    checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
441    checkValueXML(TABLE, ROW_2, COLUMN_1, VALUE_3);
442    checkValueXML(TABLE, ROW_2, COLUMN_2, VALUE_4);
443
444    response = deleteRow(TABLE, ROW_1);
445    assertEquals(200, response.getCode());
446    response = deleteRow(TABLE, ROW_2);
447    assertEquals(200, response.getCode());
448  }
449
450  @Test
451  public void testMultiCellGetPutPB() throws IOException {
452    String path = "/" + TABLE + "/fakerow"; // deliberate nonexistent row
453
454    CellSetModel cellSetModel = new CellSetModel();
455    RowModel rowModel = new RowModel(ROW_1);
456    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_1)));
457    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_2)));
458    cellSetModel.addRow(rowModel);
459    rowModel = new RowModel(ROW_2);
460    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_3)));
461    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_4)));
462    cellSetModel.addRow(rowModel);
463    Response response =
464      client.put(path, Constants.MIMETYPE_PROTOBUF, cellSetModel.createProtobufOutput());
465    Thread.yield();
466
467    // make sure the fake row was not actually created
468    response = client.get(path, Constants.MIMETYPE_PROTOBUF);
469    assertEquals(404, response.getCode());
470
471    // check that all of the values were created
472    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
473    checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
474    checkValuePB(TABLE, ROW_2, COLUMN_1, VALUE_3);
475    checkValuePB(TABLE, ROW_2, COLUMN_2, VALUE_4);
476
477    response = deleteRow(TABLE, ROW_1);
478    assertEquals(200, response.getCode());
479    response = deleteRow(TABLE, ROW_2);
480    assertEquals(200, response.getCode());
481  }
482
483  @Test
484  public void testStartEndRowGetPutXML() throws IOException, JAXBException {
485    String[] rows = { ROW_1, ROW_2, ROW_3 };
486    String[] values = { VALUE_1, VALUE_2, VALUE_3 };
487    Response response = null;
488    for (int i = 0; i < rows.length; i++) {
489      response = putValueXML(TABLE, rows[i], COLUMN_1, values[i]);
490      assertEquals(200, response.getCode());
491      checkValueXML(TABLE, rows[i], COLUMN_1, values[i]);
492    }
493    response = getValueXML(TABLE, rows[0], rows[2], COLUMN_1);
494    assertEquals(200, response.getCode());
495    CellSetModel cellSet =
496      (CellSetModel) xmlUnmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
497    assertEquals(2, cellSet.getRows().size());
498    for (int i = 0; i < cellSet.getRows().size() - 1; i++) {
499      RowModel rowModel = cellSet.getRows().get(i);
500      for (CellModel cell : rowModel.getCells()) {
501        assertEquals(COLUMN_1, Bytes.toString(cell.getColumn()));
502        assertEquals(values[i], Bytes.toString(cell.getValue()));
503      }
504    }
505    for (String row : rows) {
506      response = deleteRow(TABLE, row);
507      assertEquals(200, response.getCode());
508    }
509  }
510
511  @Test
512  public void testInvalidCheckParam() throws IOException, JAXBException {
513    CellSetModel cellSetModel = new CellSetModel();
514    RowModel rowModel = new RowModel(ROW_1);
515    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_1)));
516    cellSetModel.addRow(rowModel);
517    StringWriter writer = new StringWriter();
518    xmlMarshaller.marshal(cellSetModel, writer);
519
520    final String path = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1 + "?check=blah";
521
522    Response response = client.put(path, Constants.MIMETYPE_XML, Bytes.toBytes(writer.toString()));
523    assertEquals(400, response.getCode());
524  }
525
526  @Test
527  public void testInvalidColumnPut() throws IOException, JAXBException {
528    String dummyColumn = "doesnot:exist";
529    CellSetModel cellSetModel = new CellSetModel();
530    RowModel rowModel = new RowModel(ROW_1);
531    rowModel.addCell(new CellModel(Bytes.toBytes(dummyColumn), Bytes.toBytes(VALUE_1)));
532    cellSetModel.addRow(rowModel);
533    StringWriter writer = new StringWriter();
534    xmlMarshaller.marshal(cellSetModel, writer);
535
536    final String path = "/" + TABLE + "/" + ROW_1 + "/" + dummyColumn;
537
538    Response response = client.put(path, Constants.MIMETYPE_XML, Bytes.toBytes(writer.toString()));
539    assertEquals(404, response.getCode());
540  }
541
542  @Test
543  public void testMultiCellGetJson() throws IOException, JAXBException {
544    String path = "/" + TABLE + "/fakerow"; // deliberate nonexistent row
545
546    CellSetModel cellSetModel = new CellSetModel();
547    RowModel rowModel = new RowModel(ROW_1);
548    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_1)));
549    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_2)));
550    cellSetModel.addRow(rowModel);
551    rowModel = new RowModel(ROW_2);
552    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_3)));
553    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_4)));
554    cellSetModel.addRow(rowModel);
555    String jsonString = jsonMapper.writeValueAsString(cellSetModel);
556
557    Response response = client.put(path, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString));
558    Thread.yield();
559
560    // make sure the fake row was not actually created
561    response = client.get(path, Constants.MIMETYPE_JSON);
562    assertEquals(404, response.getCode());
563
564    // check that all of the values were created
565    checkValueJSON(TABLE, ROW_1, COLUMN_1, VALUE_1);
566    checkValueJSON(TABLE, ROW_1, COLUMN_2, VALUE_2);
567    checkValueJSON(TABLE, ROW_2, COLUMN_1, VALUE_3);
568    checkValueJSON(TABLE, ROW_2, COLUMN_2, VALUE_4);
569
570    response = deleteRow(TABLE, ROW_1);
571    assertEquals(200, response.getCode());
572    response = deleteRow(TABLE, ROW_2);
573    assertEquals(200, response.getCode());
574  }
575
576  @Test
577  public void testMetrics() throws IOException {
578    final String path = "/" + TABLE + "/" + ROW_4 + "/" + COLUMN_1;
579    Response response = client.put(path, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_4));
580    assertEquals(200, response.getCode());
581    Thread.yield();
582    response = client.get(path, Constants.MIMETYPE_JSON);
583    assertEquals(200, response.getCode());
584    assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type"));
585    response = deleteRow(TABLE, ROW_4);
586    assertEquals(200, response.getCode());
587
588    UserProvider userProvider = UserProvider.instantiate(conf);
589    METRICS_ASSERT.assertCounterGt("requests", 2L,
590      RESTServlet.getInstance(conf, userProvider).getMetrics().getSource());
591
592    METRICS_ASSERT.assertCounterGt("successfulGet", 0L,
593      RESTServlet.getInstance(conf, userProvider).getMetrics().getSource());
594
595    METRICS_ASSERT.assertCounterGt("successfulPut", 0L,
596      RESTServlet.getInstance(conf, userProvider).getMetrics().getSource());
597
598    METRICS_ASSERT.assertCounterGt("successfulDelete", 0L,
599      RESTServlet.getInstance(conf, userProvider).getMetrics().getSource());
600  }
601
602  @Test
603  public void testMultiColumnGetXML() throws Exception {
604    String path = "/" + TABLE + "/fakerow";
605    CellSetModel cellSetModel = new CellSetModel();
606    RowModel rowModel = new RowModel(ROW_1);
607    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_1)));
608    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_2)));
609    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_3), Bytes.toBytes(VALUE_2)));
610    cellSetModel.addRow(rowModel);
611    StringWriter writer = new StringWriter();
612    xmlMarshaller.marshal(cellSetModel, writer);
613
614    Response response = client.put(path, Constants.MIMETYPE_XML, Bytes.toBytes(writer.toString()));
615    Thread.yield();
616
617    // make sure the fake row was not actually created
618    response = client.get(path, Constants.MIMETYPE_XML);
619    assertEquals(404, response.getCode());
620
621    // Try getting all the column values at once.
622    path = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1 + "," + COLUMN_2 + "," + COLUMN_3;
623    response = client.get(path, Constants.MIMETYPE_XML);
624    assertEquals(200, response.getCode());
625    CellSetModel cellSet =
626      (CellSetModel) xmlUnmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
627    assertEquals(1, cellSet.getRows().size());
628    assertEquals(3, cellSet.getRows().get(0).getCells().size());
629    List<CellModel> cells = cellSet.getRows().get(0).getCells();
630
631    assertTrue(containsCellModel(cells, COLUMN_1, VALUE_1));
632    assertTrue(containsCellModel(cells, COLUMN_2, VALUE_2));
633    assertTrue(containsCellModel(cells, COLUMN_3, VALUE_2));
634    response = deleteRow(TABLE, ROW_1);
635    assertEquals(200, response.getCode());
636  }
637
638  private boolean containsCellModel(List<CellModel> cells, String column, String value) {
639    boolean contains = false;
640    for (CellModel cell : cells) {
641      if (
642        Bytes.toString(cell.getColumn()).equals(column)
643          && Bytes.toString(cell.getValue()).equals(value)
644      ) {
645        contains = true;
646        return contains;
647      }
648    }
649    return contains;
650  }
651
652  @Test
653  public void testSuffixGlobbingXMLWithNewScanner() throws IOException, JAXBException {
654    String path = "/" + TABLE + "/fakerow"; // deliberate nonexistent row
655
656    CellSetModel cellSetModel = new CellSetModel();
657    RowModel rowModel = new RowModel(ROW_1);
658    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_1)));
659    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_2)));
660    cellSetModel.addRow(rowModel);
661    rowModel = new RowModel(ROW_2);
662    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_3)));
663    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_4)));
664    cellSetModel.addRow(rowModel);
665    StringWriter writer = new StringWriter();
666    xmlMarshaller.marshal(cellSetModel, writer);
667    Response response = client.put(path, Constants.MIMETYPE_XML, Bytes.toBytes(writer.toString()));
668    Thread.yield();
669
670    // make sure the fake row was not actually created
671    response = client.get(path, Constants.MIMETYPE_XML);
672    assertEquals(404, response.getCode());
673
674    // check that all of the values were created
675    StringBuilder query = new StringBuilder();
676    query.append('/');
677    query.append(TABLE);
678    query.append('/');
679    query.append("testrow*");
680    response = client.get(query.toString(), Constants.MIMETYPE_XML);
681    assertEquals(200, response.getCode());
682    assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type"));
683    CellSetModel cellSet =
684      (CellSetModel) xmlUnmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
685    assertEquals(2, cellSet.getRows().size());
686
687    response = deleteRow(TABLE, ROW_1);
688    assertEquals(200, response.getCode());
689    response = deleteRow(TABLE, ROW_2);
690    assertEquals(200, response.getCode());
691  }
692
693  @Test
694  public void testSuffixGlobbingXML() throws IOException, JAXBException {
695    String path = "/" + TABLE + "/fakerow"; // deliberate nonexistent row
696
697    CellSetModel cellSetModel = new CellSetModel();
698    RowModel rowModel = new RowModel(ROW_1);
699    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_1)));
700    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_2)));
701    cellSetModel.addRow(rowModel);
702    rowModel = new RowModel(ROW_2);
703    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1), Bytes.toBytes(VALUE_3)));
704    rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2), Bytes.toBytes(VALUE_4)));
705    cellSetModel.addRow(rowModel);
706    StringWriter writer = new StringWriter();
707    xmlMarshaller.marshal(cellSetModel, writer);
708    Response response = client.put(path, Constants.MIMETYPE_XML, Bytes.toBytes(writer.toString()));
709    Thread.yield();
710
711    // make sure the fake row was not actually created
712    response = client.get(path, Constants.MIMETYPE_XML);
713    assertEquals(404, response.getCode());
714
715    // check that all of the values were created
716    StringBuilder query = new StringBuilder();
717    query.append('/');
718    query.append(TABLE);
719    query.append('/');
720    query.append("testrow*");
721    query.append('/');
722    query.append(COLUMN_1);
723    response = client.get(query.toString(), Constants.MIMETYPE_XML);
724    assertEquals(200, response.getCode());
725    assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type"));
726    CellSetModel cellSet =
727      (CellSetModel) xmlUnmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
728    List<RowModel> rows = cellSet.getRows();
729    assertEquals(2, rows.size());
730    for (RowModel row : rows) {
731      assertEquals(1, row.getCells().size());
732      assertEquals(COLUMN_1, Bytes.toString(row.getCells().get(0).getColumn()));
733    }
734    response = deleteRow(TABLE, ROW_1);
735    assertEquals(200, response.getCode());
736    response = deleteRow(TABLE, ROW_2);
737    assertEquals(200, response.getCode());
738  }
739
740  @Test
741  public void testAppendXML() throws IOException, JAXBException {
742    Response response = getValueXML(TABLE, ROW_1, COLUMN_1);
743    assertEquals(404, response.getCode());
744
745    // append cell
746    response = appendValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
747    assertEquals(200, response.getCode());
748    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
749    response = appendValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
750    assertEquals(200, response.getCode());
751    checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1 + VALUE_2);
752
753    response = deleteRow(TABLE, ROW_1);
754    assertEquals(200, response.getCode());
755  }
756
757  @Test
758  public void testAppendPB() throws IOException, JAXBException {
759    Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
760    assertEquals(404, response.getCode());
761
762    // append cell
763    response = appendValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
764    assertEquals(200, response.getCode());
765    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
766    response = appendValuePB(TABLE, ROW_1, COLUMN_1, VALUE_2);
767    assertEquals(200, response.getCode());
768    checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1 + VALUE_2);
769
770    response = deleteRow(TABLE, ROW_1);
771    assertEquals(200, response.getCode());
772  }
773
774  @Test
775  public void testAppendJSON() throws IOException, JAXBException {
776    Response response = getValueJson(TABLE, ROW_1, COLUMN_1);
777    assertEquals(404, response.getCode());
778
779    // append cell
780    response = appendValueJson(TABLE, ROW_1, COLUMN_1, VALUE_1);
781    assertEquals(200, response.getCode());
782    putValueJson(TABLE, ROW_1, COLUMN_1, VALUE_1);
783    response = appendValueJson(TABLE, ROW_1, COLUMN_1, VALUE_2);
784    assertEquals(200, response.getCode());
785    putValueJson(TABLE, ROW_1, COLUMN_1, VALUE_1 + VALUE_2);
786
787    response = deleteRow(TABLE, ROW_1);
788    assertEquals(200, response.getCode());
789  }
790
791  @Test
792  public void testIncrementXML() throws IOException, JAXBException {
793    Response response = getValueXML(TABLE, ROW_1, COLUMN_1);
794    assertEquals(404, response.getCode());
795
796    // append single cell
797    response = incrementValueXML(TABLE, ROW_1, COLUMN_1, VALUE_5);
798    assertEquals(200, response.getCode());
799    checkIncrementValueXML(TABLE, ROW_1, COLUMN_1, Long.parseLong(VALUE_5));
800    response = incrementValueXML(TABLE, ROW_1, COLUMN_1, VALUE_6);
801    assertEquals(200, response.getCode());
802    checkIncrementValueXML(TABLE, ROW_1, COLUMN_1,
803      Long.parseLong(VALUE_5) + Long.parseLong(VALUE_6));
804
805    response = deleteRow(TABLE, ROW_1);
806    assertEquals(200, response.getCode());
807  }
808
809  @Test
810  public void testIncrementPB() throws IOException, JAXBException {
811    Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
812    assertEquals(404, response.getCode());
813
814    // append cell
815    response = incrementValuePB(TABLE, ROW_1, COLUMN_1, VALUE_5);
816    assertEquals(200, response.getCode());
817    checkIncrementValuePB(TABLE, ROW_1, COLUMN_1, Long.parseLong(VALUE_5));
818    response = incrementValuePB(TABLE, ROW_1, COLUMN_1, VALUE_6);
819    assertEquals(200, response.getCode());
820    checkIncrementValuePB(TABLE, ROW_1, COLUMN_1,
821      Long.parseLong(VALUE_5) + Long.parseLong(VALUE_6));
822
823    response = deleteRow(TABLE, ROW_1);
824    assertEquals(200, response.getCode());
825  }
826
827  @Test
828  public void testIncrementJSON() throws IOException, JAXBException {
829    Response response = getValueJson(TABLE, ROW_1, COLUMN_1);
830    assertEquals(404, response.getCode());
831
832    // append cell
833    response = incrementValueJson(TABLE, ROW_1, COLUMN_1, VALUE_5);
834    assertEquals(200, response.getCode());
835    checkIncrementValueJSON(TABLE, ROW_1, COLUMN_1, Long.parseLong(VALUE_5));
836    response = incrementValueJson(TABLE, ROW_1, COLUMN_1, VALUE_6);
837    assertEquals(200, response.getCode());
838    checkIncrementValueJSON(TABLE, ROW_1, COLUMN_1,
839      Long.parseLong(VALUE_5) + Long.parseLong(VALUE_6));
840
841    response = deleteRow(TABLE, ROW_1);
842    assertEquals(200, response.getCode());
843  }
844}