1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.rest;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.IOException;
25  import java.io.StringWriter;
26  import java.net.URLEncoder;
27  
28  import javax.xml.bind.JAXBContext;
29  import javax.xml.bind.JAXBException;
30  import javax.xml.bind.Marshaller;
31  import javax.xml.bind.Unmarshaller;
32  
33  import org.apache.commons.httpclient.Header;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.hbase.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.HColumnDescriptor;
37  import org.apache.hadoop.hbase.HConstants;
38  import org.apache.hadoop.hbase.HTableDescriptor;
39  import org.apache.hadoop.hbase.client.HBaseAdmin;
40  import org.apache.hadoop.hbase.rest.client.Client;
41  import org.apache.hadoop.hbase.rest.client.Cluster;
42  import org.apache.hadoop.hbase.rest.client.Response;
43  import org.apache.hadoop.hbase.rest.model.CellModel;
44  import org.apache.hadoop.hbase.rest.model.CellSetModel;
45  import org.apache.hadoop.hbase.rest.model.RowModel;
46  import org.apache.hadoop.hbase.util.Bytes;
47  
48  import static org.junit.Assert.*;
49  import org.junit.AfterClass;
50  import org.junit.BeforeClass;
51  import org.junit.Test;
52  
53  public class TestRowResource {
54    private static final String TABLE = "TestRowResource";
55    private static final String CFA = "a";
56    private static final String CFB = "b";
57    private static final String COLUMN_1 = CFA + ":1";
58    private static final String COLUMN_2 = CFB + ":2";
59    private static final String ROW_1 = "testrow1";
60    private static final String VALUE_1 = "testvalue1";
61    private static final String ROW_2 = "testrow2";
62    private static final String VALUE_2 = "testvalue2";
63    private static final String ROW_3 = "testrow3";
64    private static final String VALUE_3 = "testvalue3";
65    private static final String ROW_4 = "testrow4";
66    private static final String VALUE_4 = "testvalue4";
67  
68    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
69    private static final HBaseRESTTestingUtility REST_TEST_UTIL = 
70      new HBaseRESTTestingUtility();
71    private static Client client;
72    private static JAXBContext context;
73    private static Marshaller marshaller;
74    private static Unmarshaller unmarshaller;
75    private static Configuration conf;
76  
77    @BeforeClass
78    public static void setUpBeforeClass() throws Exception {
79      conf = TEST_UTIL.getConfiguration();
80      TEST_UTIL.startMiniCluster(3);
81      REST_TEST_UTIL.startServletContainer(conf);
82      context = JAXBContext.newInstance(
83          CellModel.class,
84          CellSetModel.class,
85          RowModel.class);
86      marshaller = context.createMarshaller();
87      unmarshaller = context.createUnmarshaller();
88      client = new Client(new Cluster().add("localhost", 
89        REST_TEST_UTIL.getServletPort()));
90      HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
91      if (admin.tableExists(TABLE)) {
92        return;
93      }
94      HTableDescriptor htd = new HTableDescriptor(TABLE);
95      htd.addFamily(new HColumnDescriptor(CFA));
96      htd.addFamily(new HColumnDescriptor(CFB));
97      admin.createTable(htd);
98    }
99  
100   @AfterClass
101   public static void tearDownAfterClass() throws Exception {
102     REST_TEST_UTIL.shutdownServletContainer();
103     TEST_UTIL.shutdownMiniCluster();
104   }
105 
106   private static Response deleteRow(String table, String row) 
107       throws IOException {
108     StringBuilder path = new StringBuilder();
109     path.append('/');
110     path.append(table);
111     path.append('/');
112     path.append(row);
113     Response response = client.delete(path.toString());
114     Thread.yield();
115     return response;
116   }
117 
118   private static Response deleteValue(String table, String row, String column)
119       throws IOException {
120     StringBuilder path = new StringBuilder();
121     path.append('/');
122     path.append(table);
123     path.append('/');
124     path.append(row);
125     path.append('/');
126     path.append(column);
127     Response response = client.delete(path.toString());
128     Thread.yield();
129     return response;
130   }
131 
132   private static Response getValueXML(String table, String row, String column)
133       throws IOException {
134     StringBuilder path = new StringBuilder();
135     path.append('/');
136     path.append(table);
137     path.append('/');
138     path.append(row);
139     path.append('/');
140     path.append(column);
141     return getValueXML(path.toString());
142   }
143 
144   private static Response getValueXML(String table, String startRow,
145       String endRow, String column) throws IOException {
146     StringBuilder path = new StringBuilder();
147     path.append('/');
148     path.append(table);
149     path.append('/');
150     path.append(startRow);
151     path.append(",");
152     path.append(endRow);
153     path.append('/');
154     path.append(column);
155     return getValueXML(path.toString());
156   }
157 
158   private static Response getValueXML(String url) throws IOException {
159     Response response = client.get(url, Constants.MIMETYPE_XML);
160     return response;
161   }
162 
163   private static Response getValuePB(String table, String row, String column) 
164       throws IOException {
165     StringBuilder path = new StringBuilder();
166     path.append('/');
167     path.append(table);
168     path.append('/');
169     path.append(row);
170     path.append('/');
171     path.append(column);
172     return getValuePB(path.toString());
173   }
174 
175   private static Response getValuePB(String url) throws IOException {
176     Response response = client.get(url, Constants.MIMETYPE_PROTOBUF); 
177     return response;
178   }
179 
180   private static Response putValueXML(String table, String row, String column,
181       String value) throws IOException, JAXBException {
182     StringBuilder path = new StringBuilder();
183     path.append('/');
184     path.append(table);
185     path.append('/');
186     path.append(row);
187     path.append('/');
188     path.append(column);
189     return putValueXML(path.toString(), table, row, column, value);
190   }
191 
192   private static Response putValueXML(String url, String table, String row,
193       String column, String value) throws IOException, JAXBException {
194     RowModel rowModel = new RowModel(row);
195     rowModel.addCell(new CellModel(Bytes.toBytes(column),
196       Bytes.toBytes(value)));
197     CellSetModel cellSetModel = new CellSetModel();
198     cellSetModel.addRow(rowModel);
199     StringWriter writer = new StringWriter();
200     marshaller.marshal(cellSetModel, writer);
201     Response response = client.put(url, Constants.MIMETYPE_XML,
202       Bytes.toBytes(writer.toString()));
203     Thread.yield();
204     return response;
205   }
206 
207   private static void checkValueXML(String table, String row, String column,
208       String value) throws IOException, JAXBException {
209     Response response = getValueXML(table, row, column);
210     assertEquals(response.getCode(), 200);
211     CellSetModel cellSet = (CellSetModel)
212       unmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
213     RowModel rowModel = cellSet.getRows().get(0);
214     CellModel cell = rowModel.getCells().get(0);
215     assertEquals(Bytes.toString(cell.getColumn()), column);
216     assertEquals(Bytes.toString(cell.getValue()), value);
217   }
218 
219   private static void checkValueXML(String url, String table, String row,
220       String column, String value) throws IOException, JAXBException {
221     Response response = getValueXML(url);
222     assertEquals(response.getCode(), 200);
223     CellSetModel cellSet = (CellSetModel)
224       unmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
225     RowModel rowModel = cellSet.getRows().get(0);
226     CellModel cell = rowModel.getCells().get(0);
227     assertEquals(Bytes.toString(cell.getColumn()), column);
228     assertEquals(Bytes.toString(cell.getValue()), value);
229   }
230 
231   private static Response putValuePB(String table, String row, String column,
232       String value) throws IOException {
233     StringBuilder path = new StringBuilder();
234     path.append('/');
235     path.append(table);
236     path.append('/');
237     path.append(row);
238     path.append('/');
239     path.append(column);
240     return putValuePB(path.toString(), table, row, column, value);
241   }
242 
243   private static Response putValuePB(String url, String table, String row,
244       String column, String value) throws IOException {
245     RowModel rowModel = new RowModel(row);
246     rowModel.addCell(new CellModel(Bytes.toBytes(column),
247       Bytes.toBytes(value)));
248     CellSetModel cellSetModel = new CellSetModel();
249     cellSetModel.addRow(rowModel);
250     Response response = client.put(url, Constants.MIMETYPE_PROTOBUF,
251       cellSetModel.createProtobufOutput());
252     Thread.yield();
253     return response;
254   }
255 
256   private static void checkValuePB(String table, String row, String column, 
257       String value) throws IOException {
258     Response response = getValuePB(table, row, column);
259     assertEquals(response.getCode(), 200);
260     CellSetModel cellSet = new CellSetModel();
261     cellSet.getObjectFromMessage(response.getBody());
262     RowModel rowModel = cellSet.getRows().get(0);
263     CellModel cell = rowModel.getCells().get(0);
264     assertEquals(Bytes.toString(cell.getColumn()), column);
265     assertEquals(Bytes.toString(cell.getValue()), value);
266   }
267 
268   @Test
269   public void testDelete() throws IOException, JAXBException {
270     Response response;
271     
272     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
273     assertEquals(response.getCode(), 200);
274     response = putValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
275     assertEquals(response.getCode(), 200);
276     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
277     checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
278 
279     response = deleteValue(TABLE, ROW_1, COLUMN_1);
280     assertEquals(response.getCode(), 200);
281     response = getValueXML(TABLE, ROW_1, COLUMN_1);
282     assertEquals(response.getCode(), 404);
283     checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
284 
285     response = deleteRow(TABLE, ROW_1);
286     assertEquals(response.getCode(), 200);    
287     response = getValueXML(TABLE, ROW_1, COLUMN_1);
288     assertEquals(response.getCode(), 404);
289     response = getValueXML(TABLE, ROW_1, COLUMN_2);
290     assertEquals(response.getCode(), 404);
291   }
292 
293   @Test
294   public void testForbidden() throws IOException, JAXBException {
295     Response response;
296 
297     conf.set("hbase.rest.readonly", "true");
298 
299     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
300     assertEquals(response.getCode(), 403);
301     response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
302     assertEquals(response.getCode(), 403);
303     response = deleteValue(TABLE, ROW_1, COLUMN_1);
304     assertEquals(response.getCode(), 403);
305     response = deleteRow(TABLE, ROW_1);
306     assertEquals(response.getCode(), 403);
307 
308     conf.set("hbase.rest.readonly", "false");
309 
310     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
311     assertEquals(response.getCode(), 200);
312     response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
313     assertEquals(response.getCode(), 200);
314     response = deleteValue(TABLE, ROW_1, COLUMN_1);
315     assertEquals(response.getCode(), 200);
316     response = deleteRow(TABLE, ROW_1);
317     assertEquals(response.getCode(), 200);
318   }
319 
320   @Test
321   public void testSingleCellGetPutXML() throws IOException, JAXBException {
322     Response response = getValueXML(TABLE, ROW_1, COLUMN_1);
323     assertEquals(response.getCode(), 404);
324 
325     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
326     assertEquals(response.getCode(), 200);
327     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
328     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
329     assertEquals(response.getCode(), 200);
330     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
331 
332     response = deleteRow(TABLE, ROW_1);
333     assertEquals(response.getCode(), 200);
334   }
335 
336   @Test
337   public void testSingleCellGetPutPB() throws IOException, JAXBException {
338     Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
339     assertEquals(response.getCode(), 404);
340 
341     response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
342     assertEquals(response.getCode(), 200);
343     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
344 
345     response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
346     assertEquals(response.getCode(), 200);
347     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
348     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
349     assertEquals(response.getCode(), 200);
350     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_2);
351 
352     response = deleteRow(TABLE, ROW_1);
353     assertEquals(response.getCode(), 200);
354   }
355 
356   @Test
357   public void testSingleCellGetPutBinary() throws IOException {
358     final String path = "/" + TABLE + "/" + ROW_3 + "/" + COLUMN_1;
359     final byte[] body = Bytes.toBytes(VALUE_3);
360     Response response = client.put(path, Constants.MIMETYPE_BINARY, body);
361     assertEquals(response.getCode(), 200);
362     Thread.yield();
363 
364     response = client.get(path, Constants.MIMETYPE_BINARY);
365     assertEquals(response.getCode(), 200);
366     assertTrue(Bytes.equals(response.getBody(), body));
367     boolean foundTimestampHeader = false;
368     for (Header header: response.getHeaders()) {
369       if (header.getName().equals("X-Timestamp")) {
370         foundTimestampHeader = true;
371         break;
372       }
373     }
374     assertTrue(foundTimestampHeader);
375 
376     response = deleteRow(TABLE, ROW_3);
377     assertEquals(response.getCode(), 200);
378   }
379 
380   @Test
381   public void testSingleCellGetJSON() throws IOException, JAXBException {
382     final String path = "/" + TABLE + "/" + ROW_4 + "/" + COLUMN_1;
383     Response response = client.put(path, Constants.MIMETYPE_BINARY,
384       Bytes.toBytes(VALUE_4));
385     assertEquals(response.getCode(), 200);
386     Thread.yield();
387     response = client.get(path, Constants.MIMETYPE_JSON);
388     assertEquals(response.getCode(), 200);
389     response = deleteRow(TABLE, ROW_4);
390     assertEquals(response.getCode(), 200);
391   }
392 
393   @Test
394   public void testURLEncodedKey() throws IOException, JAXBException {
395     String urlKey = "http://example.com/foo";
396     StringBuilder path = new StringBuilder();
397     path.append('/');
398     path.append(TABLE);
399     path.append('/');
400     path.append(URLEncoder.encode(urlKey, HConstants.UTF8_ENCODING));
401     path.append('/');
402     path.append(COLUMN_1);
403     Response response;
404     response = putValueXML(path.toString(), TABLE, urlKey, COLUMN_1,
405       VALUE_1);
406     assertEquals(response.getCode(), 200);
407     checkValueXML(path.toString(), TABLE, urlKey, COLUMN_1, VALUE_1);
408   }
409 
410   @Test
411   public void testNoSuchCF() throws IOException, JAXBException {
412     final String goodPath = "/" + TABLE + "/" + ROW_1 + "/" + CFA+":";
413     final String badPath = "/" + TABLE + "/" + ROW_1 + "/" + "BAD";
414     Response response = client.post(goodPath, Constants.MIMETYPE_BINARY,
415       Bytes.toBytes(VALUE_1));
416     assertEquals(response.getCode(), 200);
417     assertEquals(client.get(goodPath, Constants.MIMETYPE_BINARY).getCode(),
418       200);
419     assertEquals(client.get(badPath, Constants.MIMETYPE_BINARY).getCode(),
420       404);
421     assertEquals(client.get(goodPath, Constants.MIMETYPE_BINARY).getCode(),
422       200);
423   }
424 
425   @Test
426   public void testMultiCellGetPutXML() throws IOException, JAXBException {
427     String path = "/" + TABLE + "/fakerow";  // deliberate nonexistent row
428 
429     CellSetModel cellSetModel = new CellSetModel();
430     RowModel rowModel = new RowModel(ROW_1);
431     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
432       Bytes.toBytes(VALUE_1)));
433     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
434       Bytes.toBytes(VALUE_2)));
435     cellSetModel.addRow(rowModel);
436     rowModel = new RowModel(ROW_2);
437     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
438       Bytes.toBytes(VALUE_3)));
439     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
440       Bytes.toBytes(VALUE_4)));
441     cellSetModel.addRow(rowModel);
442     StringWriter writer = new StringWriter();
443     marshaller.marshal(cellSetModel, writer);
444     Response response = client.put(path, Constants.MIMETYPE_XML,
445       Bytes.toBytes(writer.toString()));
446     Thread.yield();
447 
448     // make sure the fake row was not actually created
449     response = client.get(path, Constants.MIMETYPE_XML);
450     assertEquals(response.getCode(), 404);
451 
452     // check that all of the values were created
453     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
454     checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
455     checkValueXML(TABLE, ROW_2, COLUMN_1, VALUE_3);
456     checkValueXML(TABLE, ROW_2, COLUMN_2, VALUE_4);
457 
458     response = deleteRow(TABLE, ROW_1);
459     assertEquals(response.getCode(), 200);
460     response = deleteRow(TABLE, ROW_2);
461     assertEquals(response.getCode(), 200);
462   }
463 
464   @Test
465   public void testMultiCellGetPutPB() throws IOException {
466     String path = "/" + TABLE + "/fakerow";  // deliberate nonexistent row
467 
468     CellSetModel cellSetModel = new CellSetModel();
469     RowModel rowModel = new RowModel(ROW_1);
470     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
471       Bytes.toBytes(VALUE_1)));
472     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
473       Bytes.toBytes(VALUE_2)));
474     cellSetModel.addRow(rowModel);
475     rowModel = new RowModel(ROW_2);
476     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
477       Bytes.toBytes(VALUE_3)));
478     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
479       Bytes.toBytes(VALUE_4)));
480     cellSetModel.addRow(rowModel);
481     Response response = client.put(path, Constants.MIMETYPE_PROTOBUF,
482       cellSetModel.createProtobufOutput());
483     Thread.yield();
484 
485     // make sure the fake row was not actually created
486     response = client.get(path, Constants.MIMETYPE_PROTOBUF);
487     assertEquals(response.getCode(), 404);
488 
489     // check that all of the values were created
490     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
491     checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
492     checkValuePB(TABLE, ROW_2, COLUMN_1, VALUE_3);
493     checkValuePB(TABLE, ROW_2, COLUMN_2, VALUE_4);
494 
495     response = deleteRow(TABLE, ROW_1);
496     assertEquals(response.getCode(), 200);
497     response = deleteRow(TABLE, ROW_2);
498     assertEquals(response.getCode(), 200);
499   }
500 
501   @Test
502   public void testStartEndRowGetPutXML() throws IOException, JAXBException {
503     String[] rows = { ROW_1, ROW_2, ROW_3 };
504     String[] values = { VALUE_1, VALUE_2, VALUE_3 }; 
505     Response response = null;
506     for (int i = 0; i < rows.length; i++) {
507       response = putValueXML(TABLE, rows[i], COLUMN_1, values[i]);
508       assertEquals(200, response.getCode());
509       checkValueXML(TABLE, rows[i], COLUMN_1, values[i]);
510     }
511     response = getValueXML(TABLE, rows[0], rows[2], COLUMN_1);
512     assertEquals(200, response.getCode());
513     CellSetModel cellSet = (CellSetModel)
514       unmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
515     assertEquals(2, cellSet.getRows().size());
516     for (int i = 0; i < cellSet.getRows().size()-1; i++) {
517       RowModel rowModel = cellSet.getRows().get(i);
518       for (CellModel cell: rowModel.getCells()) {
519         assertEquals(COLUMN_1, Bytes.toString(cell.getColumn()));
520         assertEquals(values[i], Bytes.toString(cell.getValue()));
521       }   
522     }
523     for (String row : rows) {
524       response = deleteRow(TABLE, row);
525       assertEquals(200, response.getCode());
526     }
527   }
528 }