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