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