1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.rest;
21
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import javax.ws.rs.Consumes;
27 import javax.ws.rs.DELETE;
28 import javax.ws.rs.GET;
29 import javax.ws.rs.POST;
30 import javax.ws.rs.PUT;
31 import javax.ws.rs.Produces;
32 import javax.ws.rs.core.Context;
33 import javax.ws.rs.core.HttpHeaders;
34 import javax.ws.rs.core.MultivaluedMap;
35 import javax.ws.rs.core.Response;
36 import javax.ws.rs.core.Response.ResponseBuilder;
37 import javax.ws.rs.core.UriInfo;
38
39 import org.apache.commons.lang.StringUtils;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.apache.hadoop.hbase.classification.InterfaceAudience;
43 import org.apache.hadoop.hbase.Cell;
44 import org.apache.hadoop.hbase.CellUtil;
45 import org.apache.hadoop.hbase.HConstants;
46 import org.apache.hadoop.hbase.KeyValue;
47 import org.apache.hadoop.hbase.client.Delete;
48 import org.apache.hadoop.hbase.client.Put;
49 import org.apache.hadoop.hbase.client.Table;
50 import org.apache.hadoop.hbase.rest.model.CellModel;
51 import org.apache.hadoop.hbase.rest.model.CellSetModel;
52 import org.apache.hadoop.hbase.rest.model.RowModel;
53 import org.apache.hadoop.hbase.util.Bytes;
54
55 @InterfaceAudience.Private
56 public class RowResource extends ResourceBase {
57 private static final Log LOG = LogFactory.getLog(RowResource.class);
58
59 static final String CHECK_PUT = "put";
60 static final String CHECK_DELETE = "delete";
61
62 TableResource tableResource;
63 RowSpec rowspec;
64 private String check = null;
65
66
67
68
69
70
71
72
73 public RowResource(TableResource tableResource, String rowspec,
74 String versions, String check) throws IOException {
75 super();
76 this.tableResource = tableResource;
77 this.rowspec = new RowSpec(rowspec);
78 if (versions != null) {
79 this.rowspec.setMaxVersions(Integer.valueOf(versions));
80 }
81 this.check = check;
82 }
83
84 @GET
85 @Produces({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
86 MIMETYPE_PROTOBUF_IETF})
87 public Response get(final @Context UriInfo uriInfo) {
88 if (LOG.isTraceEnabled()) {
89 LOG.trace("GET " + uriInfo.getAbsolutePath());
90 }
91 servlet.getMetrics().incrementRequests(1);
92 MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
93 try {
94 ResultGenerator generator =
95 ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null,
96 !params.containsKey(NOCACHE_PARAM_NAME));
97 if (!generator.hasNext()) {
98 servlet.getMetrics().incrementFailedGetRequests(1);
99 return Response.status(Response.Status.NOT_FOUND)
100 .type(MIMETYPE_TEXT).entity("Not found" + CRLF)
101 .build();
102 }
103 int count = 0;
104 CellSetModel model = new CellSetModel();
105 Cell value = generator.next();
106 byte[] rowKey = CellUtil.cloneRow(value);
107 RowModel rowModel = new RowModel(rowKey);
108 do {
109 if (!Bytes.equals(CellUtil.cloneRow(value), rowKey)) {
110 model.addRow(rowModel);
111 rowKey = CellUtil.cloneRow(value);
112 rowModel = new RowModel(rowKey);
113 }
114 rowModel.addCell(new CellModel(CellUtil.cloneFamily(value), CellUtil.cloneQualifier(value),
115 value.getTimestamp(), CellUtil.cloneValue(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
138
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: Either 0 or more than 1 columns specified." + CRLF).build();
143 }
144 MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
145 try {
146 ResultGenerator generator =
147 ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null,
148 !params.containsKey(NOCACHE_PARAM_NAME));
149 if (!generator.hasNext()) {
150 servlet.getMetrics().incrementFailedGetRequests(1);
151 return Response.status(Response.Status.NOT_FOUND)
152 .type(MIMETYPE_TEXT).entity("Not found" + CRLF)
153 .build();
154 }
155 Cell value = generator.next();
156 ResponseBuilder response = Response.ok(CellUtil.cloneValue(value));
157 response.header("X-Timestamp", value.getTimestamp());
158 servlet.getMetrics().incrementSucessfulGetRequests(1);
159 return response.build();
160 } catch (Exception e) {
161 servlet.getMetrics().incrementFailedGetRequests(1);
162 return processException(e);
163 }
164 }
165
166 Response update(final CellSetModel model, final boolean replace) {
167 servlet.getMetrics().incrementRequests(1);
168 if (servlet.isReadOnly()) {
169 servlet.getMetrics().incrementFailedPutRequests(1);
170 return Response.status(Response.Status.FORBIDDEN)
171 .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
172 .build();
173 }
174
175 if (CHECK_PUT.equalsIgnoreCase(check)) {
176 return checkAndPut(model);
177 } else if (CHECK_DELETE.equalsIgnoreCase(check)) {
178 return checkAndDelete(model);
179 } else if (check != null && check.length() > 0) {
180 return Response.status(Response.Status.BAD_REQUEST)
181 .type(MIMETYPE_TEXT).entity("Invalid check value '" + check + "'" + CRLF)
182 .build();
183 }
184
185 Table table = null;
186 try {
187 List<RowModel> rows = model.getRows();
188 List<Put> puts = new ArrayList<Put>();
189 for (RowModel row: rows) {
190 byte[] key = row.getKey();
191 if (key == null) {
192 key = rowspec.getRow();
193 }
194 if (key == null) {
195 servlet.getMetrics().incrementFailedPutRequests(1);
196 return Response.status(Response.Status.BAD_REQUEST)
197 .type(MIMETYPE_TEXT).entity("Bad request: Row key not specified." + CRLF)
198 .build();
199 }
200 Put put = new Put(key);
201 int i = 0;
202 for (CellModel cell: row.getCells()) {
203 byte[] col = cell.getColumn();
204 if (col == null) try {
205 col = rowspec.getColumns()[i++];
206 } catch (ArrayIndexOutOfBoundsException e) {
207 col = null;
208 }
209 if (col == null) {
210 servlet.getMetrics().incrementFailedPutRequests(1);
211 return Response.status(Response.Status.BAD_REQUEST)
212 .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
213 .build();
214 }
215 byte [][] parts = KeyValue.parseColumn(col);
216 if (parts.length != 2) {
217 return Response.status(Response.Status.BAD_REQUEST)
218 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
219 .build();
220 }
221 put.addImmutable(parts[0], parts[1], cell.getTimestamp(), cell.getValue());
222 }
223 puts.add(put);
224 if (LOG.isTraceEnabled()) {
225 LOG.trace("PUT " + put.toString());
226 }
227 }
228 table = servlet.getTable(tableResource.getName());
229 table.put(puts);
230 ResponseBuilder response = Response.ok();
231 servlet.getMetrics().incrementSucessfulPutRequests(1);
232 return response.build();
233 } catch (Exception e) {
234 servlet.getMetrics().incrementFailedPutRequests(1);
235 return processException(e);
236 } finally {
237 if (table != null) try {
238 table.close();
239 } catch (IOException ioe) {
240 LOG.debug("Exception received while closing the table", ioe);
241 }
242 }
243 }
244
245
246 Response updateBinary(final byte[] message, final HttpHeaders headers,
247 final boolean replace) {
248 servlet.getMetrics().incrementRequests(1);
249 if (servlet.isReadOnly()) {
250 servlet.getMetrics().incrementFailedPutRequests(1);
251 return Response.status(Response.Status.FORBIDDEN)
252 .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
253 .build();
254 }
255 Table table = null;
256 try {
257 byte[] row = rowspec.getRow();
258 byte[][] columns = rowspec.getColumns();
259 byte[] column = null;
260 if (columns != null) {
261 column = columns[0];
262 }
263 long timestamp = HConstants.LATEST_TIMESTAMP;
264 List<String> vals = headers.getRequestHeader("X-Row");
265 if (vals != null && !vals.isEmpty()) {
266 row = Bytes.toBytes(vals.get(0));
267 }
268 vals = headers.getRequestHeader("X-Column");
269 if (vals != null && !vals.isEmpty()) {
270 column = Bytes.toBytes(vals.get(0));
271 }
272 vals = headers.getRequestHeader("X-Timestamp");
273 if (vals != null && !vals.isEmpty()) {
274 timestamp = Long.valueOf(vals.get(0));
275 }
276 if (column == null) {
277 servlet.getMetrics().incrementFailedPutRequests(1);
278 return Response.status(Response.Status.BAD_REQUEST)
279 .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
280 .build();
281 }
282 Put put = new Put(row);
283 byte parts[][] = KeyValue.parseColumn(column);
284 if (parts.length != 2) {
285 return Response.status(Response.Status.BAD_REQUEST)
286 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
287 .build();
288 }
289 put.addImmutable(parts[0], parts[1], timestamp, message);
290 table = servlet.getTable(tableResource.getName());
291 table.put(put);
292 if (LOG.isTraceEnabled()) {
293 LOG.trace("PUT " + put.toString());
294 }
295 servlet.getMetrics().incrementSucessfulPutRequests(1);
296 return Response.ok().build();
297 } catch (Exception e) {
298 servlet.getMetrics().incrementFailedPutRequests(1);
299 return processException(e);
300 } finally {
301 if (table != null) try {
302 table.close();
303 } catch (IOException ioe) {
304 LOG.debug("Exception received while closing the table", ioe);
305 }
306 }
307 }
308
309 @PUT
310 @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
311 MIMETYPE_PROTOBUF_IETF})
312 public Response put(final CellSetModel model,
313 final @Context UriInfo uriInfo) {
314 if (LOG.isTraceEnabled()) {
315 LOG.trace("PUT " + uriInfo.getAbsolutePath()
316 + " " + uriInfo.getQueryParameters());
317 }
318 return update(model, true);
319 }
320
321 @PUT
322 @Consumes(MIMETYPE_BINARY)
323 public Response putBinary(final byte[] message,
324 final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
325 if (LOG.isTraceEnabled()) {
326 LOG.trace("PUT " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
327 }
328 return updateBinary(message, headers, true);
329 }
330
331 @POST
332 @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
333 MIMETYPE_PROTOBUF_IETF})
334 public Response post(final CellSetModel model,
335 final @Context UriInfo uriInfo) {
336 if (LOG.isTraceEnabled()) {
337 LOG.trace("POST " + uriInfo.getAbsolutePath()
338 + " " + uriInfo.getQueryParameters());
339 }
340 return update(model, false);
341 }
342
343 @POST
344 @Consumes(MIMETYPE_BINARY)
345 public Response postBinary(final byte[] message,
346 final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
347 if (LOG.isTraceEnabled()) {
348 LOG.trace("POST " + uriInfo.getAbsolutePath() + " as "+MIMETYPE_BINARY);
349 }
350 return updateBinary(message, headers, false);
351 }
352
353 @DELETE
354 public Response delete(final @Context UriInfo uriInfo) {
355 if (LOG.isTraceEnabled()) {
356 LOG.trace("DELETE " + uriInfo.getAbsolutePath());
357 }
358 servlet.getMetrics().incrementRequests(1);
359 if (servlet.isReadOnly()) {
360 servlet.getMetrics().incrementFailedDeleteRequests(1);
361 return Response.status(Response.Status.FORBIDDEN)
362 .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
363 .build();
364 }
365 Delete delete = null;
366 if (rowspec.hasTimestamp())
367 delete = new Delete(rowspec.getRow(), rowspec.getTimestamp());
368 else
369 delete = new Delete(rowspec.getRow());
370
371 for (byte[] column: rowspec.getColumns()) {
372 byte[][] split = KeyValue.parseColumn(column);
373 if (rowspec.hasTimestamp()) {
374 if (split.length == 1) {
375 delete.deleteFamily(split[0], rowspec.getTimestamp());
376 } else if (split.length == 2) {
377 delete.deleteColumns(split[0], split[1], rowspec.getTimestamp());
378 } else {
379 return Response.status(Response.Status.BAD_REQUEST)
380 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
381 .build();
382 }
383 } else {
384 if (split.length == 1) {
385 delete.deleteFamily(split[0]);
386 } else if (split.length == 2) {
387 delete.deleteColumns(split[0], split[1]);
388 } else {
389 return Response.status(Response.Status.BAD_REQUEST)
390 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
391 .build();
392 }
393 }
394 }
395 Table table = null;
396 try {
397 table = servlet.getTable(tableResource.getName());
398 table.delete(delete);
399 servlet.getMetrics().incrementSucessfulDeleteRequests(1);
400 if (LOG.isTraceEnabled()) {
401 LOG.trace("DELETE " + delete.toString());
402 }
403 } catch (Exception e) {
404 servlet.getMetrics().incrementFailedDeleteRequests(1);
405 return processException(e);
406 } finally {
407 if (table != null) try {
408 table.close();
409 } catch (IOException ioe) {
410 LOG.debug("Exception received while closing the table", ioe);
411 }
412 }
413 return Response.ok().build();
414 }
415
416
417
418
419
420
421
422
423 Response checkAndPut(final CellSetModel model) {
424 Table table = null;
425 try {
426 table = servlet.getTable(tableResource.getName());
427 if (model.getRows().size() != 1) {
428 servlet.getMetrics().incrementFailedPutRequests(1);
429 return Response.status(Response.Status.BAD_REQUEST).type(MIMETYPE_TEXT)
430 .entity("Bad request: Number of rows specified is not 1." + CRLF).build();
431 }
432
433 RowModel rowModel = model.getRows().get(0);
434 byte[] key = rowModel.getKey();
435 if (key == null) {
436 key = rowspec.getRow();
437 }
438
439 List<CellModel> cellModels = rowModel.getCells();
440 int cellModelCount = cellModels.size();
441 if (key == null || cellModelCount <= 1) {
442 servlet.getMetrics().incrementFailedPutRequests(1);
443 return Response
444 .status(Response.Status.BAD_REQUEST)
445 .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 = KeyValue.parseColumn(valueToCheckColumn);
456 if (valueToPutParts.length == 2 && valueToPutParts[1].length > 0) {
457 CellModel valueToPutCell = null;
458
459
460
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)
468 .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
469 .build();
470 }
471
472 byte [][] parts = KeyValue.parseColumn(col);
473
474 if (parts.length != 2) {
475 return Response.status(Response.Status.BAD_REQUEST)
476 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
477 .build();
478 }
479 put.addImmutable(parts[0], parts[1], cell.getTimestamp(), cell.getValue());
480
481 if(Bytes.equals(col,
482 valueToCheckCell.getColumn())) {
483 valueToPutCell = cell;
484 }
485 }
486
487 if (valueToPutCell == null) {
488 servlet.getMetrics().incrementFailedPutRequests(1);
489 return Response.status(Response.Status.BAD_REQUEST).type(MIMETYPE_TEXT)
490 .entity("Bad request: The column to put and check do not match." + CRLF).build();
491 } else {
492 retValue = table.checkAndPut(key, valueToPutParts[0], valueToPutParts[1],
493 valueToCheckCell.getValue(), put);
494 }
495 } else {
496 servlet.getMetrics().incrementFailedPutRequests(1);
497 return Response.status(Response.Status.BAD_REQUEST)
498 .type(MIMETYPE_TEXT).entity("Bad request: Column incorrectly specified." + CRLF)
499 .build();
500 }
501
502 if (LOG.isTraceEnabled()) {
503 LOG.trace("CHECK-AND-PUT " + put.toString() + ", returns " + retValue);
504 }
505 if (!retValue) {
506 servlet.getMetrics().incrementFailedPutRequests(1);
507 return Response.status(Response.Status.NOT_MODIFIED)
508 .type(MIMETYPE_TEXT).entity("Value not Modified" + CRLF)
509 .build();
510 }
511 ResponseBuilder response = Response.ok();
512 servlet.getMetrics().incrementSucessfulPutRequests(1);
513 return response.build();
514 } catch (Exception e) {
515 servlet.getMetrics().incrementFailedPutRequests(1);
516 return processException(e);
517 } finally {
518 if (table != null) try {
519 table.close();
520 } catch (IOException ioe) {
521 LOG.debug("Exception received while closing the table", ioe);
522 }
523 }
524 }
525
526
527
528
529
530
531
532
533 Response checkAndDelete(final CellSetModel model) {
534 Table table = null;
535 Delete delete = null;
536 try {
537 table = servlet.getTable(tableResource.getName());
538 if (model.getRows().size() != 1) {
539 servlet.getMetrics().incrementFailedDeleteRequests(1);
540 return Response.status(Response.Status.BAD_REQUEST)
541 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
542 .build();
543 }
544 RowModel rowModel = model.getRows().get(0);
545 byte[] key = rowModel.getKey();
546 if (key == null) {
547 key = rowspec.getRow();
548 }
549 if (key == null) {
550 servlet.getMetrics().incrementFailedDeleteRequests(1);
551 return Response.status(Response.Status.BAD_REQUEST)
552 .type(MIMETYPE_TEXT).entity("Bad request: Row key found to be null." + CRLF)
553 .build();
554 }
555
556 List<CellModel> cellModels = rowModel.getCells();
557 int cellModelCount = cellModels.size();
558
559 delete = new Delete(key);
560 boolean retValue;
561 CellModel valueToDeleteCell = rowModel.getCells().get(cellModelCount -1);
562 byte[] valueToDeleteColumn = valueToDeleteCell.getColumn();
563 if (valueToDeleteColumn == null) {
564 try {
565 valueToDeleteColumn = rowspec.getColumns()[0];
566 } catch (final ArrayIndexOutOfBoundsException e) {
567 servlet.getMetrics().incrementFailedDeleteRequests(1);
568 return Response.status(Response.Status.BAD_REQUEST)
569 .type(MIMETYPE_TEXT).entity("Bad request: Column not specified for check." + CRLF)
570 .build();
571 }
572 }
573
574 byte[][] parts ;
575
576 if(cellModelCount > 1) {
577 for (int i = 0, n = cellModelCount - 1; i < n; i++) {
578 CellModel cell = cellModels.get(i);
579 byte[] col = cell.getColumn();
580
581 if (col == null) {
582 servlet.getMetrics().incrementFailedPutRequests(1);
583 return Response.status(Response.Status.BAD_REQUEST)
584 .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
585 .build();
586 }
587
588 parts = KeyValue.parseColumn(col);
589
590 if (parts.length == 1) {
591
592 delete.addFamily(parts[0], cell.getTimestamp());
593 } else if (parts.length == 2) {
594 delete.addColumn(parts[0], parts[1], cell.getTimestamp());
595 } else {
596 servlet.getMetrics().incrementFailedDeleteRequests(1);
597 return Response.status(Response.Status.BAD_REQUEST)
598 .type(MIMETYPE_TEXT)
599 .entity("Bad request: Column to delete incorrectly specified." + CRLF)
600 .build();
601 }
602 }
603 }
604
605 parts = KeyValue.parseColumn(valueToDeleteColumn);
606 if (parts.length == 2) {
607 if (parts[1].length != 0) {
608
609
610 if(cellModelCount == 1) {
611 delete.addColumns(parts[0], parts[1]);
612 }
613 retValue = table.checkAndDelete(key, parts[0], parts[1],
614 valueToDeleteCell.getValue(), delete);
615 } else {
616
617 if(cellModelCount == 1) {
618 delete.addColumns(parts[0], Bytes.toBytes(StringUtils.EMPTY));
619 }
620 retValue = table.checkAndDelete(key, parts[0], Bytes.toBytes(StringUtils.EMPTY),
621 valueToDeleteCell.getValue(), delete);
622 }
623 } else {
624 servlet.getMetrics().incrementFailedDeleteRequests(1);
625 return Response.status(Response.Status.BAD_REQUEST)
626 .type(MIMETYPE_TEXT).entity("Bad request: Column to check incorrectly specified." + CRLF)
627 .build();
628 }
629
630 if (LOG.isTraceEnabled()) {
631 LOG.trace("CHECK-AND-DELETE " + delete.toString() + ", returns "
632 + retValue);
633 }
634
635 if (!retValue) {
636 servlet.getMetrics().incrementFailedDeleteRequests(1);
637 return Response.status(Response.Status.NOT_MODIFIED)
638 .type(MIMETYPE_TEXT).entity(" Delete check failed." + CRLF)
639 .build();
640 }
641 ResponseBuilder response = Response.ok();
642 servlet.getMetrics().incrementSucessfulDeleteRequests(1);
643 return response.build();
644 } catch (Exception e) {
645 servlet.getMetrics().incrementFailedDeleteRequests(1);
646 return processException(e);
647 } finally {
648 if (table != null) try {
649 table.close();
650 } catch (IOException ioe) {
651 LOG.debug("Exception received while closing the table", ioe);
652 }
653 }
654 }
655 }