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.MultivaluedMap;
35  import javax.ws.rs.core.Response;
36  import javax.ws.rs.core.Response.ResponseBuilder;
37  import javax.ws.rs.core.UriInfo;
38  
39  import org.apache.commons.lang.StringUtils;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.apache.hadoop.hbase.classification.InterfaceAudience;
43  import org.apache.hadoop.hbase.Cell;
44  import org.apache.hadoop.hbase.CellUtil;
45  import org.apache.hadoop.hbase.HConstants;
46  import org.apache.hadoop.hbase.KeyValue;
47  import org.apache.hadoop.hbase.client.Append;
48  import org.apache.hadoop.hbase.client.Delete;
49  import org.apache.hadoop.hbase.client.Increment;
50  import org.apache.hadoop.hbase.client.Put;
51  import org.apache.hadoop.hbase.client.Result;
52  import org.apache.hadoop.hbase.client.Table;
53  import org.apache.hadoop.hbase.rest.model.CellModel;
54  import org.apache.hadoop.hbase.rest.model.CellSetModel;
55  import org.apache.hadoop.hbase.rest.model.RowModel;
56  import org.apache.hadoop.hbase.util.Bytes;
57  
58  @InterfaceAudience.Private
59  public class RowResource extends ResourceBase {
60    private static final Log LOG = LogFactory.getLog(RowResource.class);
61  
62    private static final String CHECK_PUT = "put";
63    private static final String CHECK_DELETE = "delete";
64    private static final String CHECK_APPEND = "append";
65    private static final String CHECK_INCREMENT = "increment";
66  
67    private TableResource tableResource;
68    private RowSpec rowspec;
69    private String check = null;
70    private boolean returnResult = false;
71  
72    /**
73     * Constructor
74     * @param tableResource
75     * @param rowspec
76     * @param versions
77     * @param check
78     * @param returnResult
79     * @throws IOException
80     */
81    public RowResource(TableResource tableResource, String rowspec,
82        String versions, String check, String returnResult) throws IOException {
83      super();
84      this.tableResource = tableResource;
85      this.rowspec = new RowSpec(rowspec);
86      if (versions != null) {
87        this.rowspec.setMaxVersions(Integer.parseInt(versions));
88      }
89      this.check = check;
90      if (returnResult != null) {
91        this.returnResult = Boolean.valueOf(returnResult);
92      }
93    }
94  
95    @GET
96    @Produces({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
97      MIMETYPE_PROTOBUF_IETF})
98    public Response get(final @Context UriInfo uriInfo) {
99      if (LOG.isTraceEnabled()) {
100       LOG.trace("GET " + uriInfo.getAbsolutePath());
101     }
102     servlet.getMetrics().incrementRequests(1);
103     MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
104     try {
105       ResultGenerator generator =
106         ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null,
107           !params.containsKey(NOCACHE_PARAM_NAME));
108       if (!generator.hasNext()) {
109         servlet.getMetrics().incrementFailedGetRequests(1);
110         return Response.status(Response.Status.NOT_FOUND)
111           .type(MIMETYPE_TEXT).entity("Not found" + CRLF)
112           .build();
113       }
114       int count = 0;
115       CellSetModel model = new CellSetModel();
116       Cell value = generator.next();
117       byte[] rowKey = CellUtil.cloneRow(value);
118       RowModel rowModel = new RowModel(rowKey);
119       do {
120         if (!Bytes.equals(CellUtil.cloneRow(value), rowKey)) {
121           model.addRow(rowModel);
122           rowKey = CellUtil.cloneRow(value);
123           rowModel = new RowModel(rowKey);
124         }
125         rowModel.addCell(new CellModel(CellUtil.cloneFamily(value), CellUtil.cloneQualifier(value),
126           value.getTimestamp(), CellUtil.cloneValue(value)));
127         if (++count > rowspec.getMaxValues()) {
128           break;
129         }
130         value = generator.next();
131       } while (value != null);
132       model.addRow(rowModel);
133       servlet.getMetrics().incrementSucessfulGetRequests(1);
134       return Response.ok(model).build();
135     } catch (Exception e) {
136       servlet.getMetrics().incrementFailedPutRequests(1);
137       return processException(e);
138     }
139   }
140 
141   @GET
142   @Produces(MIMETYPE_BINARY)
143   public Response getBinary(final @Context UriInfo uriInfo) {
144     if (LOG.isTraceEnabled()) {
145       LOG.trace("GET " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
146     }
147     servlet.getMetrics().incrementRequests(1);
148     // doesn't make sense to use a non specific coordinate as this can only
149     // return a single cell
150     if (!rowspec.hasColumns() || rowspec.getColumns().length > 1) {
151       servlet.getMetrics().incrementFailedGetRequests(1);
152       return Response.status(Response.Status.BAD_REQUEST).type(MIMETYPE_TEXT)
153           .entity("Bad request: Default 'GET' method only works if there is exactly 1 column " +
154                   "in the row. Using the 'Accept' header with one of these formats lets you " +
155                   "retrieve the entire row if it has multiple columns: " +
156                   // Same as the @Produces list for the get method.
157                   MIMETYPE_XML + ", " + MIMETYPE_JSON + ", " +
158                   MIMETYPE_PROTOBUF + ", " + MIMETYPE_PROTOBUF_IETF +
159                   CRLF).build();
160     }
161     MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
162     try {
163       ResultGenerator generator =
164         ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null,
165           !params.containsKey(NOCACHE_PARAM_NAME));
166       if (!generator.hasNext()) {
167         servlet.getMetrics().incrementFailedGetRequests(1);
168         return Response.status(Response.Status.NOT_FOUND)
169           .type(MIMETYPE_TEXT).entity("Not found" + CRLF)
170           .build();
171       }
172       Cell value = generator.next();
173       ResponseBuilder response = Response.ok(CellUtil.cloneValue(value));
174       response.header("X-Timestamp", value.getTimestamp());
175       servlet.getMetrics().incrementSucessfulGetRequests(1);
176       return response.build();
177     } catch (Exception e) {
178       servlet.getMetrics().incrementFailedGetRequests(1);
179       return processException(e);
180     }
181   }
182 
183   Response update(final CellSetModel model, final boolean replace) {
184     servlet.getMetrics().incrementRequests(1);
185     if (servlet.isReadOnly()) {
186       servlet.getMetrics().incrementFailedPutRequests(1);
187       return Response.status(Response.Status.FORBIDDEN)
188         .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
189         .build();
190     }
191 
192     if (CHECK_PUT.equalsIgnoreCase(check)) {
193       return checkAndPut(model);
194     } else if (CHECK_DELETE.equalsIgnoreCase(check)) {
195       return checkAndDelete(model);
196     } else if (CHECK_APPEND.equalsIgnoreCase(check)) {
197       return append(model);
198     } else if (CHECK_INCREMENT.equalsIgnoreCase(check)) {
199       return increment(model);
200     } else if (check != null && check.length() > 0) {
201       return Response.status(Response.Status.BAD_REQUEST)
202         .type(MIMETYPE_TEXT).entity("Invalid check value '" + check + "'" + CRLF)
203         .build();
204     }
205 
206     Table table = null;
207     try {
208       List<RowModel> rows = model.getRows();
209       List<Put> puts = new ArrayList<Put>();
210       for (RowModel row: rows) {
211         byte[] key = row.getKey();
212         if (key == null) {
213           key = rowspec.getRow();
214         }
215         if (key == null) {
216           servlet.getMetrics().incrementFailedPutRequests(1);
217           return Response.status(Response.Status.BAD_REQUEST)
218             .type(MIMETYPE_TEXT).entity("Bad request: Row key not specified." + CRLF)
219             .build();
220         }
221         Put put = new Put(key);
222         int i = 0;
223         for (CellModel cell: row.getCells()) {
224           byte[] col = cell.getColumn();
225           if (col == null) try {
226             col = rowspec.getColumns()[i++];
227           } catch (ArrayIndexOutOfBoundsException e) {
228             col = null;
229           }
230           if (col == null) {
231             servlet.getMetrics().incrementFailedPutRequests(1);
232             return Response.status(Response.Status.BAD_REQUEST)
233               .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
234               .build();
235           }
236           byte [][] parts = KeyValue.parseColumn(col);
237           if (parts.length != 2) {
238             return Response.status(Response.Status.BAD_REQUEST)
239               .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
240               .build();
241           }
242           put.addImmutable(parts[0], parts[1], cell.getTimestamp(), cell.getValue());
243         }
244         puts.add(put);
245         if (LOG.isTraceEnabled()) {
246           LOG.trace("PUT " + put.toString());
247         }
248       }
249       table = servlet.getTable(tableResource.getName());
250       table.put(puts);
251       ResponseBuilder response = Response.ok();
252       servlet.getMetrics().incrementSucessfulPutRequests(1);
253       return response.build();
254     } catch (Exception e) {
255       servlet.getMetrics().incrementFailedPutRequests(1);
256       return processException(e);
257     } finally {
258       if (table != null) try {
259         table.close();
260       } catch (IOException ioe) {
261         LOG.debug("Exception received while closing the table", ioe);
262       }
263     }
264   }
265 
266   // This currently supports only update of one row at a time.
267   Response updateBinary(final byte[] message, final HttpHeaders headers,
268       final boolean replace) {
269     servlet.getMetrics().incrementRequests(1);
270     if (servlet.isReadOnly()) {
271       servlet.getMetrics().incrementFailedPutRequests(1);
272       return Response.status(Response.Status.FORBIDDEN)
273         .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
274         .build();
275     }
276     Table table = null;
277     try {
278       byte[] row = rowspec.getRow();
279       byte[][] columns = rowspec.getColumns();
280       byte[] column = null;
281       if (columns != null) {
282         column = columns[0];
283       }
284       long timestamp = HConstants.LATEST_TIMESTAMP;
285       List<String> vals = headers.getRequestHeader("X-Row");
286       if (vals != null && !vals.isEmpty()) {
287         row = Bytes.toBytes(vals.get(0));
288       }
289       vals = headers.getRequestHeader("X-Column");
290       if (vals != null && !vals.isEmpty()) {
291         column = Bytes.toBytes(vals.get(0));
292       }
293       vals = headers.getRequestHeader("X-Timestamp");
294       if (vals != null && !vals.isEmpty()) {
295         timestamp = Long.parseLong(vals.get(0));
296       }
297       if (column == null) {
298         servlet.getMetrics().incrementFailedPutRequests(1);
299         return Response.status(Response.Status.BAD_REQUEST)
300             .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
301             .build();
302       }
303       Put put = new Put(row);
304       byte parts[][] = KeyValue.parseColumn(column);
305       if (parts.length != 2) {
306         return Response.status(Response.Status.BAD_REQUEST)
307           .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
308           .build();
309       }
310       put.addImmutable(parts[0], parts[1], timestamp, message);
311       table = servlet.getTable(tableResource.getName());
312       table.put(put);
313       if (LOG.isTraceEnabled()) {
314         LOG.trace("PUT " + put.toString());
315       }
316       servlet.getMetrics().incrementSucessfulPutRequests(1);
317       return Response.ok().build();
318     } catch (Exception e) {
319       servlet.getMetrics().incrementFailedPutRequests(1);
320       return processException(e);
321     } finally {
322       if (table != null) try {
323         table.close();
324       } catch (IOException ioe) {
325         LOG.debug("Exception received while closing the table", ioe);
326       }
327     }
328   }
329 
330   @PUT
331   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
332     MIMETYPE_PROTOBUF_IETF})
333   public Response put(final CellSetModel model,
334       final @Context UriInfo uriInfo) {
335     if (LOG.isTraceEnabled()) {
336       LOG.trace("PUT " + uriInfo.getAbsolutePath()
337         + " " + uriInfo.getQueryParameters());
338     }
339     return update(model, true);
340   }
341 
342   @PUT
343   @Consumes(MIMETYPE_BINARY)
344   public Response putBinary(final byte[] message,
345       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
346     if (LOG.isTraceEnabled()) {
347       LOG.trace("PUT " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
348     }
349     return updateBinary(message, headers, true);
350   }
351 
352   @POST
353   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
354     MIMETYPE_PROTOBUF_IETF})
355   public Response post(final CellSetModel model,
356       final @Context UriInfo uriInfo) {
357     if (LOG.isTraceEnabled()) {
358       LOG.trace("POST " + uriInfo.getAbsolutePath()
359         + " " + uriInfo.getQueryParameters());
360     }
361     return update(model, false);
362   }
363 
364   @POST
365   @Consumes(MIMETYPE_BINARY)
366   public Response postBinary(final byte[] message,
367       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
368     if (LOG.isTraceEnabled()) {
369       LOG.trace("POST " + uriInfo.getAbsolutePath() + " as "+MIMETYPE_BINARY);
370     }
371     return updateBinary(message, headers, false);
372   }
373 
374   @DELETE
375   public Response delete(final @Context UriInfo uriInfo) {
376     if (LOG.isTraceEnabled()) {
377       LOG.trace("DELETE " + uriInfo.getAbsolutePath());
378     }
379     servlet.getMetrics().incrementRequests(1);
380     if (servlet.isReadOnly()) {
381       servlet.getMetrics().incrementFailedDeleteRequests(1);
382       return Response.status(Response.Status.FORBIDDEN)
383         .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
384         .build();
385     }
386     Delete delete = null;
387     if (rowspec.hasTimestamp())
388       delete = new Delete(rowspec.getRow(), rowspec.getTimestamp());
389     else
390       delete = new Delete(rowspec.getRow());
391 
392     for (byte[] column: rowspec.getColumns()) {
393       byte[][] split = KeyValue.parseColumn(column);
394       if (rowspec.hasTimestamp()) {
395         if (split.length == 1) {
396           delete.deleteFamily(split[0], rowspec.getTimestamp());
397         } else if (split.length == 2) {
398           delete.deleteColumns(split[0], split[1], rowspec.getTimestamp());
399         } else {
400           return Response.status(Response.Status.BAD_REQUEST)
401             .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
402             .build();
403         }
404       } else {
405         if (split.length == 1) {
406           delete.deleteFamily(split[0]);
407         } else if (split.length == 2) {
408           delete.deleteColumns(split[0], split[1]);
409         } else {
410           return Response.status(Response.Status.BAD_REQUEST)
411             .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
412             .build();
413         }
414       }
415     }
416     Table table = null;
417     try {
418       table = servlet.getTable(tableResource.getName());
419       table.delete(delete);
420       servlet.getMetrics().incrementSucessfulDeleteRequests(1);
421       if (LOG.isTraceEnabled()) {
422         LOG.trace("DELETE " + delete.toString());
423       }
424     } catch (Exception e) {
425       servlet.getMetrics().incrementFailedDeleteRequests(1);
426       return processException(e);
427     } finally {
428       if (table != null) try {
429         table.close();
430       } catch (IOException ioe) {
431         LOG.debug("Exception received while closing the table", ioe);
432       }
433     }
434     return Response.ok().build();
435   }
436 
437   /**
438    * Validates the input request parameters, parses columns from CellSetModel,
439    * and invokes checkAndPut on HTable.
440    *
441    * @param model instance of CellSetModel
442    * @return Response 200 OK, 304 Not modified, 400 Bad request
443    */
444   Response checkAndPut(final CellSetModel model) {
445     Table table = null;
446     try {
447       table = servlet.getTable(tableResource.getName());
448       if (model.getRows().size() != 1) {
449         servlet.getMetrics().incrementFailedPutRequests(1);
450         return Response.status(Response.Status.BAD_REQUEST).type(MIMETYPE_TEXT)
451             .entity("Bad request: Number of rows specified is not 1." + CRLF).build();
452       }
453 
454       RowModel rowModel = model.getRows().get(0);
455       byte[] key = rowModel.getKey();
456       if (key == null) {
457         key = rowspec.getRow();
458       }
459 
460       List<CellModel> cellModels = rowModel.getCells();
461       int cellModelCount = cellModels.size();
462       if (key == null || cellModelCount <= 1) {
463         servlet.getMetrics().incrementFailedPutRequests(1);
464         return Response
465             .status(Response.Status.BAD_REQUEST)
466             .type(MIMETYPE_TEXT)
467             .entity(
468               "Bad request: Either row key is null or no data found for columns specified." + CRLF)
469             .build();
470       }
471 
472       Put put = new Put(key);
473       boolean retValue;
474       CellModel valueToCheckCell = cellModels.get(cellModelCount - 1);
475       byte[] valueToCheckColumn = valueToCheckCell.getColumn();
476       byte[][] valueToPutParts = KeyValue.parseColumn(valueToCheckColumn);
477       if (valueToPutParts.length == 2 && valueToPutParts[1].length > 0) {
478         CellModel valueToPutCell = null;
479 
480         // Copy all the cells to the Put request
481         // and track if the check cell's latest value is also sent
482         for (int i = 0, n = cellModelCount - 1; i < n ; i++) {
483           CellModel cell = cellModels.get(i);
484           byte[] col = cell.getColumn();
485 
486           if (col == null) {
487             servlet.getMetrics().incrementFailedPutRequests(1);
488             return Response.status(Response.Status.BAD_REQUEST)
489                     .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
490                     .build();
491           }
492 
493           byte [][] parts = KeyValue.parseColumn(col);
494 
495           if (parts.length != 2) {
496             return Response.status(Response.Status.BAD_REQUEST)
497                     .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
498                     .build();
499           }
500           put.addImmutable(parts[0], parts[1], cell.getTimestamp(), cell.getValue());
501 
502           if(Bytes.equals(col,
503                   valueToCheckCell.getColumn())) {
504             valueToPutCell = cell;
505           }
506         }
507 
508         if (valueToPutCell == null) {
509           servlet.getMetrics().incrementFailedPutRequests(1);
510           return Response.status(Response.Status.BAD_REQUEST).type(MIMETYPE_TEXT)
511               .entity("Bad request: The column to put and check do not match." + CRLF).build();
512         } else {
513           retValue = table.checkAndPut(key, valueToPutParts[0], valueToPutParts[1],
514             valueToCheckCell.getValue(), put);
515         }
516       } else {
517         servlet.getMetrics().incrementFailedPutRequests(1);
518         return Response.status(Response.Status.BAD_REQUEST)
519           .type(MIMETYPE_TEXT).entity("Bad request: Column incorrectly specified." + CRLF)
520           .build();
521       }
522 
523       if (LOG.isTraceEnabled()) {
524         LOG.trace("CHECK-AND-PUT " + put.toString() + ", returns " + retValue);
525       }
526       if (!retValue) {
527         servlet.getMetrics().incrementFailedPutRequests(1);
528         return Response.status(Response.Status.NOT_MODIFIED)
529           .type(MIMETYPE_TEXT).entity("Value not Modified" + CRLF)
530           .build();
531       }
532       ResponseBuilder response = Response.ok();
533       servlet.getMetrics().incrementSucessfulPutRequests(1);
534       return response.build();
535     } catch (Exception e) {
536       servlet.getMetrics().incrementFailedPutRequests(1);
537       return processException(e);
538     } finally {
539       if (table != null) try {
540         table.close();
541       } catch (IOException ioe) {
542         LOG.debug("Exception received while closing the table", ioe);
543       }
544     }
545   }
546 
547   /**
548    * Validates the input request parameters, parses columns from CellSetModel,
549    * and invokes checkAndDelete on HTable.
550    *
551    * @param model instance of CellSetModel
552    * @return Response 200 OK, 304 Not modified, 400 Bad request
553    */
554   Response checkAndDelete(final CellSetModel model) {
555     Table table = null;
556     Delete delete = null;
557     try {
558       table = servlet.getTable(tableResource.getName());
559       if (model.getRows().size() != 1) {
560         servlet.getMetrics().incrementFailedDeleteRequests(1);
561         return Response.status(Response.Status.BAD_REQUEST)
562           .type(MIMETYPE_TEXT).entity("Bad request: Number of rows specified is not 1." + CRLF)
563           .build();
564       }
565       RowModel rowModel = model.getRows().get(0);
566       byte[] key = rowModel.getKey();
567       if (key == null) {
568         key = rowspec.getRow();
569       }
570       if (key == null) {
571         servlet.getMetrics().incrementFailedDeleteRequests(1);
572         return Response.status(Response.Status.BAD_REQUEST)
573           .type(MIMETYPE_TEXT).entity("Bad request: Row key found to be null." + CRLF)
574           .build();
575       }
576 
577       List<CellModel> cellModels = rowModel.getCells();
578       int cellModelCount = cellModels.size();
579 
580       delete = new Delete(key);
581       boolean retValue;
582       CellModel valueToDeleteCell = rowModel.getCells().get(cellModelCount -1);
583       byte[] valueToDeleteColumn = valueToDeleteCell.getColumn();
584       if (valueToDeleteColumn == null) {
585         try {
586           valueToDeleteColumn = rowspec.getColumns()[0];
587         } catch (final ArrayIndexOutOfBoundsException e) {
588           servlet.getMetrics().incrementFailedDeleteRequests(1);
589           return Response.status(Response.Status.BAD_REQUEST)
590             .type(MIMETYPE_TEXT).entity("Bad request: Column not specified for check." + CRLF)
591             .build();
592         }
593       }
594 
595       byte[][] parts ;
596       // Copy all the cells to the Delete request if extra cells are sent
597       if(cellModelCount > 1) {
598         for (int i = 0, n = cellModelCount - 1; i < n; i++) {
599           CellModel cell = cellModels.get(i);
600           byte[] col = cell.getColumn();
601 
602           if (col == null) {
603             servlet.getMetrics().incrementFailedPutRequests(1);
604             return Response.status(Response.Status.BAD_REQUEST)
605                     .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
606                     .build();
607           }
608 
609           parts = KeyValue.parseColumn(col);
610 
611           if (parts.length == 1) {
612             // Only Column Family is specified
613             delete.addFamily(parts[0], cell.getTimestamp());
614           } else if (parts.length == 2) {
615             delete.addColumn(parts[0], parts[1], cell.getTimestamp());
616           } else {
617             servlet.getMetrics().incrementFailedDeleteRequests(1);
618             return Response.status(Response.Status.BAD_REQUEST)
619                     .type(MIMETYPE_TEXT)
620                     .entity("Bad request: Column to delete incorrectly specified." + CRLF)
621                     .build();
622           }
623         }
624       }
625 
626       parts = KeyValue.parseColumn(valueToDeleteColumn);
627       if (parts.length == 2) {
628         if (parts[1].length != 0) {
629           // To support backcompat of deleting a cell
630           // if that is the only cell passed to the rest api
631           if(cellModelCount == 1) {
632             delete.addColumns(parts[0], parts[1]);
633           }
634           retValue = table.checkAndDelete(key, parts[0], parts[1],
635             valueToDeleteCell.getValue(), delete);
636         } else {
637           // The case of empty qualifier.
638           if(cellModelCount == 1) {
639             delete.addColumns(parts[0], Bytes.toBytes(StringUtils.EMPTY));
640           }
641           retValue = table.checkAndDelete(key, parts[0], Bytes.toBytes(StringUtils.EMPTY),
642             valueToDeleteCell.getValue(), delete);
643         }
644       } else {
645         servlet.getMetrics().incrementFailedDeleteRequests(1);
646         return Response.status(Response.Status.BAD_REQUEST)
647           .type(MIMETYPE_TEXT).entity("Bad request: Column to check incorrectly specified." + CRLF)
648           .build();
649       }
650 
651       if (LOG.isTraceEnabled()) {
652         LOG.trace("CHECK-AND-DELETE " + delete.toString() + ", returns "
653           + retValue);
654       }
655 
656       if (!retValue) {
657         servlet.getMetrics().incrementFailedDeleteRequests(1);
658         return Response.status(Response.Status.NOT_MODIFIED)
659             .type(MIMETYPE_TEXT).entity(" Delete check failed." + CRLF)
660             .build();
661       }
662       ResponseBuilder response = Response.ok();
663       servlet.getMetrics().incrementSucessfulDeleteRequests(1);
664       return response.build();
665     } catch (Exception e) {
666       servlet.getMetrics().incrementFailedDeleteRequests(1);
667       return processException(e);
668     } finally {
669       if (table != null) try {
670         table.close();
671       } catch (IOException ioe) {
672         LOG.debug("Exception received while closing the table", ioe);
673       }
674     }
675   }
676 
677   /**
678    * Validates the input request parameters, parses columns from CellSetModel,
679    * and invokes Append on HTable.
680    *
681    * @param model instance of CellSetModel
682    * @return Response 200 OK, 304 Not modified, 400 Bad request
683    */
684   Response append(final CellSetModel model) {
685     Table table = null;
686     Append append = null;
687     try {
688       table = servlet.getTable(tableResource.getName());
689       if (model.getRows().size() != 1) {
690         servlet.getMetrics().incrementFailedAppendRequests(1);
691         return Response.status(Response.Status.BAD_REQUEST)
692                 .type(MIMETYPE_TEXT).entity("Bad request: Number of rows specified is not 1." + CRLF)
693                 .build();
694       }
695       RowModel rowModel = model.getRows().get(0);
696       byte[] key = rowModel.getKey();
697       if (key == null) {
698         key = rowspec.getRow();
699       }
700       if (key == null) {
701         servlet.getMetrics().incrementFailedAppendRequests(1);
702         return Response.status(Response.Status.BAD_REQUEST)
703                 .type(MIMETYPE_TEXT).entity("Bad request: Row key found to be null." + CRLF)
704                 .build();
705       }
706 
707       append = new Append(key);
708       append.setReturnResults(returnResult);
709       int i = 0;
710       for (CellModel cell: rowModel.getCells()) {
711         byte[] col = cell.getColumn();
712         if (col == null) {
713           try {
714             col = rowspec.getColumns()[i++];
715           } catch (ArrayIndexOutOfBoundsException e) {
716             col = null;
717           }
718         }
719         if (col == null) {
720           servlet.getMetrics().incrementFailedAppendRequests(1);
721           return Response.status(Response.Status.BAD_REQUEST)
722                   .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
723                   .build();
724         }
725         byte [][] parts = KeyValue.parseColumn(col);
726         if (parts.length != 2) {
727           servlet.getMetrics().incrementFailedAppendRequests(1);
728           return Response.status(Response.Status.BAD_REQUEST)
729                   .type(MIMETYPE_TEXT).entity("Bad request: Column incorrectly specified." + CRLF)
730                   .build();
731         }
732         append.add(parts[0], parts[1], cell.getValue());
733       }
734 
735       if (LOG.isDebugEnabled()) {
736         LOG.debug("APPEND " + append.toString());
737       }
738       Result result = table.append(append);
739       if (returnResult) {
740         if (result.isEmpty()) {
741           servlet.getMetrics().incrementFailedAppendRequests(1);
742           return Response.status(Response.Status.NOT_MODIFIED)
743                   .type(MIMETYPE_TEXT).entity("Append return empty." + CRLF)
744                   .build();
745         }
746 
747         CellSetModel rModel = new CellSetModel();
748         RowModel rRowModel = new RowModel(result.getRow());
749         for (Cell cell : result.listCells()) {
750           rRowModel.addCell(new CellModel(CellUtil.cloneFamily(cell), CellUtil.cloneQualifier(cell),
751                   cell.getTimestamp(), CellUtil.cloneValue(cell)));
752         }
753         rModel.addRow(rRowModel);
754         servlet.getMetrics().incrementSucessfulAppendRequests(1);
755         return Response.ok(rModel).build();
756       }
757       servlet.getMetrics().incrementSucessfulAppendRequests(1);
758       return Response.ok().build();
759     } catch (Exception e) {
760       servlet.getMetrics().incrementFailedAppendRequests(1);
761       return processException(e);
762     } finally {
763       if (table != null) try {
764         table.close();
765       } catch (IOException ioe) {
766         LOG.debug("Exception received while closing the table" + table.getName(), ioe);
767       }
768     }
769   }
770 
771   /**
772    * Validates the input request parameters, parses columns from CellSetModel,
773    * and invokes Increment on HTable.
774    *
775    * @param model instance of CellSetModel
776    * @return Response 200 OK, 304 Not modified, 400 Bad request
777    */
778   Response increment(final CellSetModel model) {
779     Table table = null;
780     Increment increment = null;
781     try {
782       table = servlet.getTable(tableResource.getName());
783       if (model.getRows().size() != 1) {
784         servlet.getMetrics().incrementFailedIncrementRequests(1);
785         return Response.status(Response.Status.BAD_REQUEST)
786                 .type(MIMETYPE_TEXT).entity("Bad request: Number of rows specified is not 1." + CRLF)
787                 .build();
788       }
789       RowModel rowModel = model.getRows().get(0);
790       byte[] key = rowModel.getKey();
791       if (key == null) {
792         key = rowspec.getRow();
793       }
794       if (key == null) {
795         servlet.getMetrics().incrementFailedIncrementRequests(1);
796         return Response.status(Response.Status.BAD_REQUEST)
797                 .type(MIMETYPE_TEXT).entity("Bad request: Row key found to be null." + CRLF)
798                 .build();
799       }
800 
801       increment = new Increment(key);
802       increment.setReturnResults(returnResult);
803       int i = 0;
804       for (CellModel cell: rowModel.getCells()) {
805         byte[] col = cell.getColumn();
806         if (col == null) {
807           try {
808             col = rowspec.getColumns()[i++];
809           } catch (ArrayIndexOutOfBoundsException e) {
810             col = null;
811           }
812         }
813         if (col == null) {
814           servlet.getMetrics().incrementFailedIncrementRequests(1);
815           return Response.status(Response.Status.BAD_REQUEST)
816                   .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
817                   .build();
818         }
819         byte [][] parts = KeyValue.parseColumn(col);
820         if (parts.length != 2) {
821           servlet.getMetrics().incrementFailedIncrementRequests(1);
822           return Response.status(Response.Status.BAD_REQUEST)
823                   .type(MIMETYPE_TEXT).entity("Bad request: Column incorrectly specified." + CRLF)
824                   .build();
825         }
826         increment.addColumn(parts[0], parts[1], Long.parseLong(Bytes.toStringBinary(cell.getValue())));
827       }
828 
829       if (LOG.isDebugEnabled()) {
830         LOG.debug("INCREMENT " + increment.toString());
831       }
832       Result result = table.increment(increment);
833 
834       if (returnResult) {
835         if (result.isEmpty()) {
836           servlet.getMetrics().incrementFailedIncrementRequests(1);
837           return Response.status(Response.Status.NOT_MODIFIED)
838                   .type(MIMETYPE_TEXT).entity("Increment return empty." + CRLF)
839                   .build();
840         }
841 
842         CellSetModel rModel = new CellSetModel();
843         RowModel rRowModel = new RowModel(result.getRow());
844         for (Cell cell : result.listCells()) {
845           rRowModel.addCell(new CellModel(CellUtil.cloneFamily(cell), CellUtil.cloneQualifier(cell),
846                   cell.getTimestamp(), CellUtil.cloneValue(cell)));
847         }
848         rModel.addRow(rowModel);
849         servlet.getMetrics().incrementSucessfulIncrementRequests(1);
850         return Response.ok(rModel).build();
851       }
852 
853       ResponseBuilder response = Response.ok();
854       servlet.getMetrics().incrementSucessfulIncrementRequests(1);
855       return response.build();
856     } catch (Exception e) {
857       servlet.getMetrics().incrementFailedIncrementRequests(1);
858       return processException(e);
859     } finally {
860       if (table != null) try {
861         table.close();
862       } catch (IOException ioe) {
863         LOG.debug("Exception received while closing the table " + table.getName(), ioe);
864       }
865     }
866   }
867 }