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