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}