View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.rest;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import javax.ws.rs.Consumes;
27  import javax.ws.rs.DELETE;
28  import javax.ws.rs.GET;
29  import javax.ws.rs.POST;
30  import javax.ws.rs.PUT;
31  import javax.ws.rs.Produces;
32  import javax.ws.rs.core.Context;
33  import javax.ws.rs.core.HttpHeaders;
34  import javax.ws.rs.core.Response;
35  import javax.ws.rs.core.UriInfo;
36  import javax.ws.rs.core.Response.ResponseBuilder;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.classification.InterfaceAudience;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.KeyValue;
43  import org.apache.hadoop.hbase.TableNotFoundException;
44  import org.apache.hadoop.hbase.client.Delete;
45  import org.apache.hadoop.hbase.client.HTableInterface;
46  import org.apache.hadoop.hbase.client.HTablePool;
47  import org.apache.hadoop.hbase.client.Put;
48  import org.apache.hadoop.hbase.rest.model.CellModel;
49  import org.apache.hadoop.hbase.rest.model.CellSetModel;
50  import org.apache.hadoop.hbase.rest.model.RowModel;
51  import org.apache.hadoop.hbase.util.Bytes;
52  
53  @InterfaceAudience.Private
54  public class RowResource extends ResourceBase {
55    private static final Log LOG = LogFactory.getLog(RowResource.class);
56  
57    static final String CHECK_PUT = "put";
58    static final String CHECK_DELETE = "delete";
59  
60    TableResource tableResource;
61    RowSpec rowspec;
62    private String check = null;
63  
64    /**
65     * Constructor
66     * @param tableResource
67     * @param rowspec
68     * @param versions
69     * @throws IOException
70     */
71    public RowResource(TableResource tableResource, String rowspec,
72        String versions, String check) throws IOException {
73      super();
74      this.tableResource = tableResource;
75      this.rowspec = new RowSpec(rowspec);
76      if (versions != null) {
77        this.rowspec.setMaxVersions(Integer.valueOf(versions));
78      }
79      this.check = check;
80    }
81  
82    @GET
83    @Produces({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
84      MIMETYPE_PROTOBUF_IETF})
85    public Response get(final @Context UriInfo uriInfo) {
86      if (LOG.isDebugEnabled()) {
87        LOG.debug("GET " + uriInfo.getAbsolutePath());
88      }
89      servlet.getMetrics().incrementRequests(1);
90      try {
91        ResultGenerator generator =
92          ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null);
93        if (!generator.hasNext()) {
94          return Response.status(Response.Status.NOT_FOUND)
95            .type(MIMETYPE_TEXT).entity("Not found" + CRLF)
96            .build();
97        }
98        int count = 0;
99        CellSetModel model = new CellSetModel();
100       KeyValue value = generator.next();
101       byte[] rowKey = value.getRow();
102       RowModel rowModel = new RowModel(rowKey);
103       do {
104         if (!Bytes.equals(value.getRow(), rowKey)) {
105           model.addRow(rowModel);
106           rowKey = value.getRow();
107           rowModel = new RowModel(rowKey);
108         }
109         rowModel.addCell(new CellModel(value.getFamily(), value.getQualifier(),
110           value.getTimestamp(), value.getValue()));
111         if (++count > rowspec.getMaxValues()) {
112           break;
113         }
114         value = generator.next();
115       } while (value != null);
116       model.addRow(rowModel);
117       servlet.getMetrics().incrementSucessfulGetRequests(1);
118       return Response.ok(model).build();
119     } catch (RuntimeException e) {
120       servlet.getMetrics().incrementFailedPutRequests(1);
121       if (e.getCause() instanceof TableNotFoundException) {
122         return Response.status(Response.Status.NOT_FOUND)
123           .type(MIMETYPE_TEXT).entity("Not found" + CRLF)
124           .build();
125       }
126       return Response.status(Response.Status.BAD_REQUEST)
127         .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
128         .build();
129     } catch (Exception e) {
130       servlet.getMetrics().incrementFailedPutRequests(1);
131       return Response.status(Response.Status.SERVICE_UNAVAILABLE)
132         .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
133         .build();
134     }
135   }
136 
137   @GET
138   @Produces(MIMETYPE_BINARY)
139   public Response getBinary(final @Context UriInfo uriInfo) {
140     if (LOG.isDebugEnabled()) {
141       LOG.debug("GET " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
142     }
143     servlet.getMetrics().incrementRequests(1);
144     // doesn't make sense to use a non specific coordinate as this can only
145     // return a single cell
146     if (!rowspec.hasColumns() || rowspec.getColumns().length > 1) {
147       return Response.status(Response.Status.BAD_REQUEST)
148         .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
149         .build();
150     }
151     try {
152       ResultGenerator generator =
153         ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null);
154       if (!generator.hasNext()) {
155         return Response.status(Response.Status.NOT_FOUND)
156           .type(MIMETYPE_TEXT).entity("Not found" + CRLF)
157           .build();
158       }
159       KeyValue value = generator.next();
160       ResponseBuilder response = Response.ok(value.getValue());
161       response.header("X-Timestamp", value.getTimestamp());
162       servlet.getMetrics().incrementSucessfulGetRequests(1);
163       return response.build();
164     } catch (IOException e) {
165       servlet.getMetrics().incrementFailedGetRequests(1);
166       return Response.status(Response.Status.SERVICE_UNAVAILABLE)
167         .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
168         .build();
169     }
170   }
171 
172   Response update(final CellSetModel model, final boolean replace) {
173     servlet.getMetrics().incrementRequests(1);
174     if (servlet.isReadOnly()) {
175       return Response.status(Response.Status.FORBIDDEN)
176         .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
177         .build();
178     }
179 
180     if (CHECK_PUT.equalsIgnoreCase(check)) {
181       return checkAndPut(model);
182     } else if (CHECK_DELETE.equalsIgnoreCase(check)) {
183       return checkAndDelete(model);
184     } else if (check != null && check.length() > 0) {
185       return Response.status(Response.Status.BAD_REQUEST)
186         .type(MIMETYPE_TEXT).entity("Invalid check value '" + check + "'" + CRLF)
187         .build();
188     }
189 
190     HTablePool pool = servlet.getTablePool();
191     HTableInterface table = null;
192     try {
193       List<RowModel> rows = model.getRows();
194       List<Put> puts = new ArrayList<Put>();
195       for (RowModel row: rows) {
196         byte[] key = row.getKey();
197         if (key == null) {
198           key = rowspec.getRow();
199         }
200         if (key == null) {
201           return Response.status(Response.Status.BAD_REQUEST)
202             .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
203             .build();
204         }
205         Put put = new Put(key);
206         int i = 0;
207         for (CellModel cell: row.getCells()) {
208           byte[] col = cell.getColumn();
209           if (col == null) try {
210             col = rowspec.getColumns()[i++];
211           } catch (ArrayIndexOutOfBoundsException e) {
212             col = null;
213           }
214           if (col == null) {
215             return Response.status(Response.Status.BAD_REQUEST)
216               .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
217               .build();
218           }
219           byte [][] parts = KeyValue.parseColumn(col);
220           if (parts.length == 2 && parts[1].length > 0) {
221             put.add(parts[0], parts[1], cell.getTimestamp(), cell.getValue());
222           } else {
223             put.add(parts[0], null, cell.getTimestamp(), cell.getValue());
224           }
225         }
226         puts.add(put);
227         if (LOG.isDebugEnabled()) {
228           LOG.debug("PUT " + put.toString());
229         }
230       }
231       table = pool.getTable(tableResource.getName());
232       table.put(puts);
233       table.flushCommits();
234       ResponseBuilder response = Response.ok();
235       servlet.getMetrics().incrementSucessfulPutRequests(1);
236       return response.build();
237     } catch (IOException e) {
238       servlet.getMetrics().incrementFailedPutRequests(1);
239       return Response.status(Response.Status.SERVICE_UNAVAILABLE)
240         .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
241         .build();
242     } finally {
243       if (table != null) try {
244         table.close();
245       } catch (IOException ioe) {
246         LOG.debug("Exception received while closing the table", ioe);
247       }
248     }
249   }
250 
251   // This currently supports only update of one row at a time.
252   Response updateBinary(final byte[] message, final HttpHeaders headers,
253       final boolean replace) {
254     servlet.getMetrics().incrementRequests(1);
255     if (servlet.isReadOnly()) {
256       return Response.status(Response.Status.FORBIDDEN)
257         .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
258         .build();
259     }
260     HTablePool pool = servlet.getTablePool();
261     HTableInterface table = null;
262     try {
263       byte[] row = rowspec.getRow();
264       byte[][] columns = rowspec.getColumns();
265       byte[] column = null;
266       if (columns != null) {
267         column = columns[0];
268       }
269       long timestamp = HConstants.LATEST_TIMESTAMP;
270       List<String> vals = headers.getRequestHeader("X-Row");
271       if (vals != null && !vals.isEmpty()) {
272         row = Bytes.toBytes(vals.get(0));
273       }
274       vals = headers.getRequestHeader("X-Column");
275       if (vals != null && !vals.isEmpty()) {
276         column = Bytes.toBytes(vals.get(0));
277       }
278       vals = headers.getRequestHeader("X-Timestamp");
279       if (vals != null && !vals.isEmpty()) {
280         timestamp = Long.valueOf(vals.get(0));
281       }
282       if (column == null) {
283         return Response.status(Response.Status.BAD_REQUEST)
284           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
285           .build();
286       }
287       Put put = new Put(row);
288       byte parts[][] = KeyValue.parseColumn(column);
289       if (parts.length == 2 && parts[1].length > 0) {
290         put.add(parts[0], parts[1], timestamp, message);
291       } else {
292         put.add(parts[0], null, timestamp, message);
293       }
294       table = pool.getTable(tableResource.getName());
295       table.put(put);
296       if (LOG.isDebugEnabled()) {
297         LOG.debug("PUT " + put.toString());
298       }
299       servlet.getMetrics().incrementSucessfulPutRequests(1);
300       return Response.ok().build();
301     } catch (IOException e) {
302       servlet.getMetrics().incrementFailedPutRequests(1);
303       return Response.status(Response.Status.SERVICE_UNAVAILABLE)
304         .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
305         .build();
306     } finally {
307       if (table != null) try {
308         table.close();
309       } catch (IOException ioe) { }
310     }
311   }
312 
313   @PUT
314   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
315     MIMETYPE_PROTOBUF_IETF})
316   public Response put(final CellSetModel model,
317       final @Context UriInfo uriInfo) {
318     if (LOG.isDebugEnabled()) {
319       LOG.debug("PUT " + uriInfo.getAbsolutePath()
320         + " " + uriInfo.getQueryParameters());
321     }
322     return update(model, true);
323   }
324 
325   @PUT
326   @Consumes(MIMETYPE_BINARY)
327   public Response putBinary(final byte[] message,
328       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
329     if (LOG.isDebugEnabled()) {
330       LOG.debug("PUT " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
331     }
332     return updateBinary(message, headers, true);
333   }
334 
335   @POST
336   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
337     MIMETYPE_PROTOBUF_IETF})
338   public Response post(final CellSetModel model,
339       final @Context UriInfo uriInfo) {
340     if (LOG.isDebugEnabled()) {
341       LOG.debug("POST " + uriInfo.getAbsolutePath()
342         + " " + uriInfo.getQueryParameters());
343     }
344     return update(model, false);
345   }
346 
347   @POST
348   @Consumes(MIMETYPE_BINARY)
349   public Response postBinary(final byte[] message,
350       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
351     if (LOG.isDebugEnabled()) {
352       LOG.debug("POST " + uriInfo.getAbsolutePath() + " as "+MIMETYPE_BINARY);
353     }
354     return updateBinary(message, headers, false);
355   }
356 
357   @DELETE
358   public Response delete(final @Context UriInfo uriInfo) {
359     if (LOG.isDebugEnabled()) {
360       LOG.debug("DELETE " + uriInfo.getAbsolutePath());
361     }
362     servlet.getMetrics().incrementRequests(1);
363     if (servlet.isReadOnly()) {
364       return Response.status(Response.Status.FORBIDDEN)
365         .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
366         .build();
367     }
368     Delete delete = null;
369     if (rowspec.hasTimestamp())
370       delete = new Delete(rowspec.getRow(), rowspec.getTimestamp());
371     else
372       delete = new Delete(rowspec.getRow());
373 
374     for (byte[] column: rowspec.getColumns()) {
375       byte[][] split = KeyValue.parseColumn(column);
376       if (rowspec.hasTimestamp()) {
377         if (split.length == 2 && split[1].length != 0) {
378           delete.deleteColumns(split[0], split[1], rowspec.getTimestamp());
379         } else {
380           delete.deleteFamily(split[0], rowspec.getTimestamp());
381         }
382       } else {
383         if (split.length == 2 && split[1].length != 0) {
384           delete.deleteColumns(split[0], split[1]);
385         } else {
386           delete.deleteFamily(split[0]);
387         }
388       }
389     }
390     HTablePool pool = servlet.getTablePool();
391     HTableInterface table = null;
392     try {
393       table = pool.getTable(tableResource.getName());
394       table.delete(delete);
395       servlet.getMetrics().incrementSucessfulDeleteRequests(1);
396       if (LOG.isDebugEnabled()) {
397         LOG.debug("DELETE " + delete.toString());
398       }
399     } catch (IOException e) {
400       servlet.getMetrics().incrementFailedDeleteRequests(1);
401       return Response.status(Response.Status.SERVICE_UNAVAILABLE)
402         .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
403         .build();
404     } finally {
405       if (table != null) try {
406         table.close();
407       } catch (IOException ioe) { }
408     }
409     return Response.ok().build();
410   }
411 
412   /**
413    * Validates the input request parameters, parses columns from CellSetModel,
414    * and invokes checkAndPut on HTable.
415    *
416    * @param model instance of CellSetModel
417    * @return Response 200 OK, 304 Not modified, 400 Bad request
418    */
419   Response checkAndPut(final CellSetModel model) {
420     HTablePool pool = servlet.getTablePool();
421     HTableInterface table = null;
422     try {
423       if (model.getRows().size() != 1) {
424         return Response.status(Response.Status.BAD_REQUEST)
425           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
426           .build();
427       }
428 
429       RowModel rowModel = model.getRows().get(0);
430       byte[] key = rowModel.getKey();
431       if (key == null) {
432         key = rowspec.getRow();
433       }
434 
435       List<CellModel> cellModels = rowModel.getCells();
436       int cellModelCount = cellModels.size();
437       if (key == null || cellModelCount <= 1) {
438         return Response.status(Response.Status.BAD_REQUEST)
439           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
440           .build();
441       }
442 
443       Put put = new Put(key);
444       CellModel valueToCheckCell = cellModels.get(cellModelCount - 1);
445       byte[] valueToCheckColumn = valueToCheckCell.getColumn();
446       byte[][] valueToPutParts = KeyValue.parseColumn(valueToCheckColumn);
447       if (valueToPutParts.length == 2 && valueToPutParts[1].length > 0) {
448         CellModel valueToPutCell = null;
449         for (int i = 0, n = cellModelCount - 1; i < n ; i++) {
450           if(Bytes.equals(cellModels.get(i).getColumn(),
451               valueToCheckCell.getColumn())) {
452             valueToPutCell = cellModels.get(i);
453             break;
454           }
455         }
456         if (valueToPutCell != null) {
457           put.add(valueToPutParts[0], valueToPutParts[1], valueToPutCell
458             .getTimestamp(), valueToPutCell.getValue());
459         } else {
460           return Response.status(Response.Status.BAD_REQUEST)
461             .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
462             .build();
463         }
464       } else {
465         return Response.status(Response.Status.BAD_REQUEST)
466           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
467           .build();
468       }
469 
470       table = pool.getTable(this.tableResource.getName());
471       boolean retValue = table.checkAndPut(key, valueToPutParts[0],
472         valueToPutParts[1], valueToCheckCell.getValue(), put);
473       if (LOG.isDebugEnabled()) {
474         LOG.debug("CHECK-AND-PUT " + put.toString() + ", returns " + retValue);
475       }
476       table.flushCommits();
477       ResponseBuilder response = Response.ok();
478       if (!retValue) {
479         response = Response.status(304);
480       }
481       return response.build();
482     } catch (IOException e) {
483       return Response.status(Response.Status.SERVICE_UNAVAILABLE)
484         .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
485         .build();
486     } finally {
487       if (table != null) try {
488         table.close();
489       } catch (IOException ioe) { }
490     }
491   }
492 
493   /**
494    * Validates the input request parameters, parses columns from CellSetModel,
495    * and invokes checkAndDelete on HTable.
496    *
497    * @param model instance of CellSetModel
498    * @return Response 200 OK, 304 Not modified, 400 Bad request
499    */
500   Response checkAndDelete(final CellSetModel model) {
501     HTablePool pool = servlet.getTablePool();
502     HTableInterface table = null;
503     Delete delete = null;
504     try {
505       if (model.getRows().size() != 1) {
506         return Response.status(Response.Status.BAD_REQUEST)
507           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
508           .build();
509       }
510       RowModel rowModel = model.getRows().get(0);
511       byte[] key = rowModel.getKey();
512       if (key == null) {
513         key = rowspec.getRow();
514       }
515       if (key == null) {
516         return Response.status(Response.Status.BAD_REQUEST)
517           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
518           .build();
519       }
520 
521       delete = new Delete(key);
522       CellModel valueToDeleteCell = rowModel.getCells().get(0);
523       byte[] valueToDeleteColumn = valueToDeleteCell.getColumn();
524       if (valueToDeleteColumn == null) {
525         try {
526           valueToDeleteColumn = rowspec.getColumns()[0];
527         } catch (final ArrayIndexOutOfBoundsException e) {
528           return Response.status(Response.Status.BAD_REQUEST)
529             .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
530             .build();
531         }
532       }
533       byte[][] parts = KeyValue.parseColumn(valueToDeleteColumn);
534       if (parts.length == 2 && parts[1].length > 0) {
535         delete.deleteColumns(parts[0], parts[1]);
536       } else {
537         return Response.status(Response.Status.BAD_REQUEST)
538           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
539           .build();
540       }
541 
542       table = pool.getTable(tableResource.getName());
543       boolean retValue = table.checkAndDelete(key, parts[0], parts[1],
544         valueToDeleteCell.getValue(), delete);
545       if (LOG.isDebugEnabled()) {
546         LOG.debug("CHECK-AND-DELETE " + delete.toString() + ", returns "
547           + retValue);
548       }
549       table.flushCommits();
550       ResponseBuilder response = Response.ok();
551       if (!retValue) {
552         response = Response.status(304);
553       }
554       return response.build();
555     } catch (IOException e) {
556       return Response.status(Response.Status.SERVICE_UNAVAILABLE)
557         .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
558         .build();
559     } finally {
560       if (table != null) try {
561         table.close();
562       } catch (IOException ioe) {
563         LOG.debug("Exception received while closing the table", ioe);
564       }
565     }
566   }
567 }