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