View Javadoc

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