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}