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.hbase.HColumnDescriptor;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HTableDescriptor;
37  import org.apache.hadoop.hbase.client.HBaseAdmin;
38  import org.apache.hadoop.hbase.rest.client.Client;
39  import org.apache.hadoop.hbase.rest.client.Cluster;
40  import org.apache.hadoop.hbase.rest.client.Response;
41  import org.apache.hadoop.hbase.rest.model.CellModel;
42  import org.apache.hadoop.hbase.rest.model.CellSetModel;
43  import org.apache.hadoop.hbase.rest.model.RowModel;
44  import org.apache.hadoop.hbase.util.Bytes;
45  
46  public class TestRowResource extends HBaseRESTClusterTestBase {
47    static final String TABLE = "TestRowResource";
48    static final String CFA = "a";
49    static final String CFB = "b";
50    static final String COLUMN_1 = CFA + ":1";
51    static final String COLUMN_2 = CFB + ":2";
52    static final String ROW_1 = "testrow1";
53    static final String VALUE_1 = "testvalue1";
54    static final String ROW_2 = "testrow2";
55    static final String VALUE_2 = "testvalue2";
56    static final String ROW_3 = "testrow3";
57    static final String VALUE_3 = "testvalue3";
58    static final String ROW_4 = "testrow4";
59    static final String VALUE_4 = "testvalue4";
60  
61    Client client;
62    JAXBContext context;
63    Marshaller marshaller;
64    Unmarshaller unmarshaller;
65    HBaseAdmin admin;
66  
67    @Override
68    protected void setUp() throws Exception {
69      super.setUp();
70      context = JAXBContext.newInstance(
71          CellModel.class,
72          CellSetModel.class,
73          RowModel.class);
74      marshaller = context.createMarshaller();
75      unmarshaller = context.createUnmarshaller();
76      client = new Client(new Cluster().add("localhost", testServletPort));
77      admin = new HBaseAdmin(conf);
78      if (admin.tableExists(TABLE)) {
79        return;
80      }
81      HTableDescriptor htd = new HTableDescriptor(TABLE);
82      htd.addFamily(new HColumnDescriptor(CFA));
83      htd.addFamily(new HColumnDescriptor(CFB));
84      admin.createTable(htd);
85    }
86  
87    @Override
88    protected void tearDown() throws Exception {
89      client.shutdown();
90      super.tearDown();
91    }
92  
93    Response deleteRow(String table, String row) throws IOException {
94      StringBuilder path = new StringBuilder();
95      path.append('/');
96      path.append(table);
97      path.append('/');
98      path.append(row);
99      Response response = client.delete(path.toString());
100     Thread.yield();
101     return response;
102   }
103 
104   Response deleteValue(String table, String row, String column)
105       throws IOException {
106     StringBuilder path = new StringBuilder();
107     path.append('/');
108     path.append(table);
109     path.append('/');
110     path.append(row);
111     path.append('/');
112     path.append(column);
113     Response response = client.delete(path.toString());
114     Thread.yield();
115     return response;
116   }
117 
118   Response getValueXML(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.get(path.toString(), MIMETYPE_XML);
128     return response;
129   }
130 
131   Response getValuePB(String table, String row, String column) 
132       throws IOException {
133     StringBuilder path = new StringBuilder();
134     path.append('/');
135     path.append(table);
136     path.append('/');
137     path.append(row);
138     path.append('/');
139     path.append(column);
140     Response response = client.get(path.toString(), MIMETYPE_PROTOBUF); 
141     return response;
142   }
143 
144   Response putValueXML(String table, String row, String column, String value)
145       throws IOException, JAXBException {
146     StringBuilder path = new StringBuilder();
147     path.append('/');
148     path.append(table);
149     path.append('/');
150     path.append(row);
151     path.append('/');
152     path.append(column);
153     RowModel rowModel = new RowModel(row);
154     rowModel.addCell(new CellModel(Bytes.toBytes(column),
155       Bytes.toBytes(value)));
156     CellSetModel cellSetModel = new CellSetModel();
157     cellSetModel.addRow(rowModel);
158     StringWriter writer = new StringWriter();
159     marshaller.marshal(cellSetModel, writer);
160     Response response = client.put(path.toString(), MIMETYPE_XML,
161       Bytes.toBytes(writer.toString()));
162     Thread.yield();
163     return response;
164   }
165 
166   void checkValueXML(String table, String row, String column, String value)
167       throws IOException, JAXBException {
168     Response response = getValueXML(table, row, column);
169     assertEquals(response.getCode(), 200);
170     CellSetModel cellSet = (CellSetModel)
171       unmarshaller.unmarshal(new ByteArrayInputStream(response.getBody()));
172     RowModel rowModel = cellSet.getRows().get(0);
173     CellModel cell = rowModel.getCells().get(0);
174     assertEquals(Bytes.toString(cell.getColumn()), column);
175     assertEquals(Bytes.toString(cell.getValue()), value);
176   }
177 
178   Response putValuePB(String table, String row, String column, String value)
179       throws IOException {
180     StringBuilder path = new StringBuilder();
181     path.append('/');
182     path.append(table);
183     path.append('/');
184     path.append(row);
185     path.append('/');
186     path.append(column);
187     RowModel rowModel = new RowModel(row);
188     rowModel.addCell(new CellModel(Bytes.toBytes(column),
189       Bytes.toBytes(value)));
190     CellSetModel cellSetModel = new CellSetModel();
191     cellSetModel.addRow(rowModel);
192     Response response = client.put(path.toString(), MIMETYPE_PROTOBUF,
193       cellSetModel.createProtobufOutput());
194     Thread.yield();
195     return response;
196   }
197 
198   void checkValuePB(String table, String row, String column, String value)
199       throws IOException {
200     Response response = getValuePB(table, row, column);
201     assertEquals(response.getCode(), 200);
202     CellSetModel cellSet = new CellSetModel();
203     cellSet.getObjectFromMessage(response.getBody());
204     RowModel rowModel = cellSet.getRows().get(0);
205     CellModel cell = rowModel.getCells().get(0);
206     assertEquals(Bytes.toString(cell.getColumn()), column);
207     assertEquals(Bytes.toString(cell.getValue()), value);
208   }
209 
210   void doTestDelete() throws IOException, JAXBException {
211     Response response;
212     
213     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
214     assertEquals(response.getCode(), 200);
215     response = putValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
216     assertEquals(response.getCode(), 200);
217     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
218     checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
219 
220     response = deleteValue(TABLE, ROW_1, COLUMN_1);
221     assertEquals(response.getCode(), 200);
222     response = getValueXML(TABLE, ROW_1, COLUMN_1);
223     assertEquals(response.getCode(), 404);
224     checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
225 
226     response = deleteRow(TABLE, ROW_1);
227     assertEquals(response.getCode(), 200);    
228     response = getValueXML(TABLE, ROW_1, COLUMN_1);
229     assertEquals(response.getCode(), 404);
230     response = getValueXML(TABLE, ROW_1, COLUMN_2);
231     assertEquals(response.getCode(), 404);
232   }
233 
234   void doTestSingleCellGetPutXML() throws IOException, JAXBException {
235     Response response = getValueXML(TABLE, ROW_1, COLUMN_1);
236     assertEquals(response.getCode(), 404);
237 
238     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
239     assertEquals(response.getCode(), 200);
240     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
241     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
242     assertEquals(response.getCode(), 200);
243     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
244 
245     response = deleteRow(TABLE, ROW_1);
246     assertEquals(response.getCode(), 200);    
247   }
248 
249   void doTestSingleCellGetPutPB() throws IOException, JAXBException {
250     Response response = getValuePB(TABLE, ROW_1, COLUMN_1);
251     assertEquals(response.getCode(), 404);
252 
253     response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
254     assertEquals(response.getCode(), 200);
255     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
256 
257     response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
258     assertEquals(response.getCode(), 200);
259     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
260     response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
261     assertEquals(response.getCode(), 200);
262     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_2);
263 
264     response = deleteRow(TABLE, ROW_1);
265     assertEquals(response.getCode(), 200);    
266   }
267 
268   void doTestSingleCellGetPutBinary() throws IOException {
269     final String path = "/" + TABLE + "/" + ROW_3 + "/" + COLUMN_1;
270     final byte[] body = Bytes.toBytes(VALUE_3);
271     Response response = client.put(path, MIMETYPE_BINARY, body);
272     assertEquals(response.getCode(), 200);
273     Thread.yield();
274 
275     response = client.get(path, MIMETYPE_BINARY);
276     assertEquals(response.getCode(), 200);
277     assertTrue(Bytes.equals(response.getBody(), body));
278     boolean foundTimestampHeader = false;
279     for (Header header: response.getHeaders()) {
280       if (header.getName().equals("X-Timestamp")) {
281         foundTimestampHeader = true;
282         break;
283       }
284     }
285     assertTrue(foundTimestampHeader);
286 
287     response = deleteRow(TABLE, ROW_3);
288     assertEquals(response.getCode(), 200);
289   }
290 
291   void doTestSingleCellGetJSON() throws IOException, JAXBException {
292     final String path = "/" + TABLE + "/" + ROW_4 + "/" + COLUMN_1;
293     Response response = client.put(path, MIMETYPE_BINARY,
294       Bytes.toBytes(VALUE_4));
295     assertEquals(response.getCode(), 200);
296     Thread.yield();
297     response = client.get(path, MIMETYPE_JSON);
298     assertEquals(response.getCode(), 200);
299     response = deleteRow(TABLE, ROW_4);
300     assertEquals(response.getCode(), 200);
301   }
302 
303   void doTestURLEncodedKey() throws IOException, JAXBException {
304     String encodedKey = URLEncoder.encode("http://www.google.com/", 
305       HConstants.UTF8_ENCODING);
306     Response response;
307     response = putValueXML(TABLE, encodedKey, COLUMN_1, VALUE_1);
308     assertEquals(response.getCode(), 200);
309     response = putValuePB(TABLE, encodedKey, COLUMN_2, VALUE_2);
310     assertEquals(response.getCode(), 200);
311     checkValuePB(TABLE, encodedKey, COLUMN_1, VALUE_1);
312     checkValueXML(TABLE, encodedKey, COLUMN_2, VALUE_2);
313   }
314 
315   void doTestMultiCellGetPutXML() throws IOException, JAXBException {
316     String path = "/" + TABLE + "/fakerow";  // deliberate nonexistent row
317 
318     CellSetModel cellSetModel = new CellSetModel();
319     RowModel rowModel = new RowModel(ROW_1);
320     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
321       Bytes.toBytes(VALUE_1)));
322     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
323       Bytes.toBytes(VALUE_2)));
324     cellSetModel.addRow(rowModel);
325     rowModel = new RowModel(ROW_2);
326     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
327       Bytes.toBytes(VALUE_3)));
328     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
329       Bytes.toBytes(VALUE_4)));
330     cellSetModel.addRow(rowModel);
331     StringWriter writer = new StringWriter();
332     marshaller.marshal(cellSetModel, writer);
333     Response response = client.put(path, MIMETYPE_XML,
334       Bytes.toBytes(writer.toString()));
335     Thread.yield();
336 
337     // make sure the fake row was not actually created
338     response = client.get(path, MIMETYPE_XML);
339     assertEquals(response.getCode(), 404);
340 
341     // check that all of the values were created
342     checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
343     checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2);
344     checkValueXML(TABLE, ROW_2, COLUMN_1, VALUE_3);
345     checkValueXML(TABLE, ROW_2, COLUMN_2, VALUE_4);
346 
347     response = deleteRow(TABLE, ROW_1);
348     assertEquals(response.getCode(), 200);    
349     response = deleteRow(TABLE, ROW_2);
350     assertEquals(response.getCode(), 200);
351   }
352 
353   void doTestMultiCellGetPutPB() throws IOException {
354     String path = "/" + TABLE + "/fakerow";  // deliberate nonexistent row
355 
356     CellSetModel cellSetModel = new CellSetModel();
357     RowModel rowModel = new RowModel(ROW_1);
358     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
359       Bytes.toBytes(VALUE_1)));
360     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
361       Bytes.toBytes(VALUE_2)));
362     cellSetModel.addRow(rowModel);
363     rowModel = new RowModel(ROW_2);
364     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_1),
365       Bytes.toBytes(VALUE_3)));
366     rowModel.addCell(new CellModel(Bytes.toBytes(COLUMN_2),
367       Bytes.toBytes(VALUE_4)));
368     cellSetModel.addRow(rowModel);
369     Response response = client.put(path, MIMETYPE_PROTOBUF,
370       cellSetModel.createProtobufOutput());
371     Thread.yield();
372 
373     // make sure the fake row was not actually created
374     response = client.get(path, MIMETYPE_PROTOBUF);
375     assertEquals(response.getCode(), 404);
376 
377     // check that all of the values were created
378     checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
379     checkValuePB(TABLE, ROW_1, COLUMN_2, VALUE_2);
380     checkValuePB(TABLE, ROW_2, COLUMN_1, VALUE_3);
381     checkValuePB(TABLE, ROW_2, COLUMN_2, VALUE_4);
382 
383     response = deleteRow(TABLE, ROW_1);
384     assertEquals(response.getCode(), 200);    
385     response = deleteRow(TABLE, ROW_2);
386     assertEquals(response.getCode(), 200);
387   }
388 
389   public void testRowResource() throws Exception {
390     doTestDelete();
391     doTestSingleCellGetPutXML();
392     doTestSingleCellGetPutPB();
393     doTestSingleCellGetPutBinary();
394     doTestSingleCellGetJSON();
395     doTestURLEncodedKey();
396     doTestMultiCellGetPutXML();
397     doTestMultiCellGetPutPB();
398   }
399 }