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.HTableInterface;
49 import org.apache.hadoop.hbase.client.Put;
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.isDebugEnabled()) {
89 LOG.debug("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.isDebugEnabled()) {
134 LOG.debug("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 HTableInterface 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.isDebugEnabled()) {
225 LOG.debug("PUT " + put.toString());
226 }
227 }
228 table = servlet.getTable(tableResource.getName());
229 table.put(puts);
230 table.flushCommits();
231 ResponseBuilder response = Response.ok();
232 servlet.getMetrics().incrementSucessfulPutRequests(1);
233 return response.build();
234 } catch (Exception e) {
235 servlet.getMetrics().incrementFailedPutRequests(1);
236 return processException(e);
237 } finally {
238 if (table != null) try {
239 table.close();
240 } catch (IOException ioe) {
241 LOG.debug("Exception received while closing the table", ioe);
242 }
243 }
244 }
245
246
247 Response updateBinary(final byte[] message, final HttpHeaders headers,
248 final boolean replace) {
249 servlet.getMetrics().incrementRequests(1);
250 if (servlet.isReadOnly()) {
251 servlet.getMetrics().incrementFailedPutRequests(1);
252 return Response.status(Response.Status.FORBIDDEN)
253 .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
254 .build();
255 }
256 HTableInterface table = null;
257 try {
258 byte[] row = rowspec.getRow();
259 byte[][] columns = rowspec.getColumns();
260 byte[] column = null;
261 if (columns != null) {
262 column = columns[0];
263 }
264 long timestamp = HConstants.LATEST_TIMESTAMP;
265 List<String> vals = headers.getRequestHeader("X-Row");
266 if (vals != null && !vals.isEmpty()) {
267 row = Bytes.toBytes(vals.get(0));
268 }
269 vals = headers.getRequestHeader("X-Column");
270 if (vals != null && !vals.isEmpty()) {
271 column = Bytes.toBytes(vals.get(0));
272 }
273 vals = headers.getRequestHeader("X-Timestamp");
274 if (vals != null && !vals.isEmpty()) {
275 timestamp = Long.valueOf(vals.get(0));
276 }
277 if (column == null) {
278 servlet.getMetrics().incrementFailedPutRequests(1);
279 return Response.status(Response.Status.BAD_REQUEST)
280 .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
281 .build();
282 }
283 Put put = new Put(row);
284 byte parts[][] = KeyValue.parseColumn(column);
285 if (parts.length != 2) {
286 return Response.status(Response.Status.BAD_REQUEST)
287 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
288 .build();
289 }
290 put.addImmutable(parts[0], parts[1], timestamp, message);
291 table = servlet.getTable(tableResource.getName());
292 table.put(put);
293 if (LOG.isDebugEnabled()) {
294 LOG.debug("PUT " + put.toString());
295 }
296 servlet.getMetrics().incrementSucessfulPutRequests(1);
297 return Response.ok().build();
298 } catch (Exception e) {
299 servlet.getMetrics().incrementFailedPutRequests(1);
300 return processException(e);
301 } finally {
302 if (table != null) try {
303 table.close();
304 } catch (IOException ioe) {
305 LOG.debug(ioe);
306 }
307 }
308 }
309
310 @PUT
311 @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
312 MIMETYPE_PROTOBUF_IETF})
313 public Response put(final CellSetModel model,
314 final @Context UriInfo uriInfo) {
315 if (LOG.isDebugEnabled()) {
316 LOG.debug("PUT " + uriInfo.getAbsolutePath()
317 + " " + uriInfo.getQueryParameters());
318 }
319 return update(model, true);
320 }
321
322 @PUT
323 @Consumes(MIMETYPE_BINARY)
324 public Response putBinary(final byte[] message,
325 final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
326 if (LOG.isDebugEnabled()) {
327 LOG.debug("PUT " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
328 }
329 return updateBinary(message, headers, true);
330 }
331
332 @POST
333 @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
334 MIMETYPE_PROTOBUF_IETF})
335 public Response post(final CellSetModel model,
336 final @Context UriInfo uriInfo) {
337 if (LOG.isDebugEnabled()) {
338 LOG.debug("POST " + uriInfo.getAbsolutePath()
339 + " " + uriInfo.getQueryParameters());
340 }
341 return update(model, false);
342 }
343
344 @POST
345 @Consumes(MIMETYPE_BINARY)
346 public Response postBinary(final byte[] message,
347 final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
348 if (LOG.isDebugEnabled()) {
349 LOG.debug("POST " + uriInfo.getAbsolutePath() + " as "+MIMETYPE_BINARY);
350 }
351 return updateBinary(message, headers, false);
352 }
353
354 @DELETE
355 public Response delete(final @Context UriInfo uriInfo) {
356 if (LOG.isDebugEnabled()) {
357 LOG.debug("DELETE " + uriInfo.getAbsolutePath());
358 }
359 servlet.getMetrics().incrementRequests(1);
360 if (servlet.isReadOnly()) {
361 servlet.getMetrics().incrementFailedDeleteRequests(1);
362 return Response.status(Response.Status.FORBIDDEN)
363 .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
364 .build();
365 }
366 Delete delete = null;
367 if (rowspec.hasTimestamp())
368 delete = new Delete(rowspec.getRow(), rowspec.getTimestamp());
369 else
370 delete = new Delete(rowspec.getRow());
371
372 for (byte[] column: rowspec.getColumns()) {
373 byte[][] split = KeyValue.parseColumn(column);
374 if (rowspec.hasTimestamp()) {
375 if (split.length == 1) {
376 delete.deleteFamily(split[0], rowspec.getTimestamp());
377 } else if (split.length == 2) {
378 delete.deleteColumns(split[0], split[1], rowspec.getTimestamp());
379 } else {
380 return Response.status(Response.Status.BAD_REQUEST)
381 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
382 .build();
383 }
384 } else {
385 if (split.length == 1) {
386 delete.deleteFamily(split[0]);
387 } else if (split.length == 2) {
388 delete.deleteColumns(split[0], split[1]);
389 } else {
390 return Response.status(Response.Status.BAD_REQUEST)
391 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
392 .build();
393 }
394 }
395 }
396 HTableInterface table = null;
397 try {
398 table = servlet.getTable(tableResource.getName());
399 table.delete(delete);
400 servlet.getMetrics().incrementSucessfulDeleteRequests(1);
401 if (LOG.isDebugEnabled()) {
402 LOG.debug("DELETE " + delete.toString());
403 }
404 } catch (Exception e) {
405 servlet.getMetrics().incrementFailedDeleteRequests(1);
406 return processException(e);
407 } finally {
408 if (table != null) try {
409 table.close();
410 } catch (IOException ioe) {
411 LOG.debug(ioe);
412 }
413 }
414 return Response.ok().build();
415 }
416
417
418
419
420
421
422
423
424 Response checkAndPut(final CellSetModel model) {
425 HTableInterface table = null;
426 try {
427 table = servlet.getTable(tableResource.getName());
428 if (model.getRows().size() != 1) {
429 servlet.getMetrics().incrementFailedPutRequests(1);
430 return Response.status(Response.Status.BAD_REQUEST).type(MIMETYPE_TEXT)
431 .entity("Bad request: Number of rows specified is not 1." + CRLF).build();
432 }
433
434 RowModel rowModel = model.getRows().get(0);
435 byte[] key = rowModel.getKey();
436 if (key == null) {
437 key = rowspec.getRow();
438 }
439
440 List<CellModel> cellModels = rowModel.getCells();
441 int cellModelCount = cellModels.size();
442 if (key == null || cellModelCount <= 1) {
443 servlet.getMetrics().incrementFailedPutRequests(1);
444 return Response
445 .status(Response.Status.BAD_REQUEST)
446 .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 = KeyValue.parseColumn(valueToCheckColumn);
457 if (valueToPutParts.length == 2 && valueToPutParts[1].length > 0) {
458 CellModel valueToPutCell = null;
459
460
461
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)
469 .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
470 .build();
471 }
472
473 byte [][] parts = KeyValue.parseColumn(col);
474
475 if (parts.length != 2) {
476 return Response.status(Response.Status.BAD_REQUEST)
477 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
478 .build();
479 }
480 put.addImmutable(parts[0], parts[1], cell.getTimestamp(), cell.getValue());
481
482 if(Bytes.equals(col,
483 valueToCheckCell.getColumn())) {
484 valueToPutCell = cell;
485 }
486 }
487
488 if (valueToPutCell == null) {
489 servlet.getMetrics().incrementFailedPutRequests(1);
490 return Response.status(Response.Status.BAD_REQUEST).type(MIMETYPE_TEXT)
491 .entity("Bad request: The column to put and check do not match." + CRLF).build();
492 } else {
493 retValue = table.checkAndPut(key, valueToPutParts[0], valueToPutParts[1],
494 valueToCheckCell.getValue(), put);
495 }
496 } else {
497 servlet.getMetrics().incrementFailedPutRequests(1);
498 return Response.status(Response.Status.BAD_REQUEST)
499 .type(MIMETYPE_TEXT).entity("Bad request: Column incorrectly specified." + CRLF)
500 .build();
501 }
502
503 if (LOG.isDebugEnabled()) {
504 LOG.debug("CHECK-AND-PUT " + put.toString() + ", returns " + retValue);
505 }
506 if (!retValue) {
507 servlet.getMetrics().incrementFailedPutRequests(1);
508 return Response.status(Response.Status.NOT_MODIFIED)
509 .type(MIMETYPE_TEXT).entity("Value not Modified" + CRLF)
510 .build();
511 }
512 table.flushCommits();
513 ResponseBuilder response = Response.ok();
514 servlet.getMetrics().incrementSucessfulPutRequests(1);
515 return response.build();
516 } catch (Exception e) {
517 servlet.getMetrics().incrementFailedPutRequests(1);
518 return processException(e);
519 } finally {
520 if (table != null) try {
521 table.close();
522 } catch (IOException ioe) {
523 LOG.debug("Exception received while closing the table", ioe);
524 }
525 }
526 }
527
528
529
530
531
532
533
534
535 Response checkAndDelete(final CellSetModel model) {
536 HTableInterface table = null;
537 Delete delete = null;
538 try {
539 table = servlet.getTable(tableResource.getName());
540 if (model.getRows().size() != 1) {
541 servlet.getMetrics().incrementFailedDeleteRequests(1);
542 return Response.status(Response.Status.BAD_REQUEST)
543 .type(MIMETYPE_TEXT).entity("Bad request" + CRLF)
544 .build();
545 }
546 RowModel rowModel = model.getRows().get(0);
547 byte[] key = rowModel.getKey();
548 if (key == null) {
549 key = rowspec.getRow();
550 }
551 if (key == null) {
552 servlet.getMetrics().incrementFailedDeleteRequests(1);
553 return Response.status(Response.Status.BAD_REQUEST)
554 .type(MIMETYPE_TEXT).entity("Bad request: Row key found to be null." + CRLF)
555 .build();
556 }
557
558 List<CellModel> cellModels = rowModel.getCells();
559 int cellModelCount = cellModels.size();
560
561 delete = new Delete(key);
562 boolean retValue;
563 CellModel valueToDeleteCell = rowModel.getCells().get(cellModelCount -1);
564 byte[] valueToDeleteColumn = valueToDeleteCell.getColumn();
565 if (valueToDeleteColumn == null) {
566 try {
567 valueToDeleteColumn = rowspec.getColumns()[0];
568 } catch (final ArrayIndexOutOfBoundsException e) {
569 servlet.getMetrics().incrementFailedDeleteRequests(1);
570 return Response.status(Response.Status.BAD_REQUEST)
571 .type(MIMETYPE_TEXT).entity("Bad request: Column not specified for check." + CRLF)
572 .build();
573 }
574 }
575
576 byte[][] parts ;
577
578 if(cellModelCount > 1) {
579 for (int i = 0, n = cellModelCount - 1; i < n; i++) {
580 CellModel cell = cellModels.get(i);
581 byte[] col = cell.getColumn();
582
583 if (col == null) {
584 servlet.getMetrics().incrementFailedPutRequests(1);
585 return Response.status(Response.Status.BAD_REQUEST)
586 .type(MIMETYPE_TEXT).entity("Bad request: Column found to be null." + CRLF)
587 .build();
588 }
589
590 parts = KeyValue.parseColumn(col);
591
592 if (parts.length == 1) {
593
594 delete.deleteFamily(parts[0], cell.getTimestamp());
595 } else if (parts.length == 2) {
596 delete.deleteColumn(parts[0], parts[1], cell.getTimestamp());
597 } else {
598 servlet.getMetrics().incrementFailedDeleteRequests(1);
599 return Response.status(Response.Status.BAD_REQUEST)
600 .type(MIMETYPE_TEXT)
601 .entity("Bad request: Column to delete incorrectly specified." + CRLF)
602 .build();
603 }
604 }
605 }
606
607 parts = KeyValue.parseColumn(valueToDeleteColumn);
608 if (parts.length == 2) {
609 if (parts[1].length != 0) {
610
611
612 if(cellModelCount == 1) {
613 delete.deleteColumns(parts[0], parts[1]);
614 }
615 retValue = table.checkAndDelete(key, parts[0], parts[1],
616 valueToDeleteCell.getValue(), delete);
617 } else {
618
619 if(cellModelCount == 1) {
620 delete.deleteColumns(parts[0], Bytes.toBytes(StringUtils.EMPTY));
621 }
622 retValue = table.checkAndDelete(key, parts[0], Bytes.toBytes(StringUtils.EMPTY),
623 valueToDeleteCell.getValue(), delete);
624 }
625 } else {
626 servlet.getMetrics().incrementFailedDeleteRequests(1);
627 return Response.status(Response.Status.BAD_REQUEST)
628 .type(MIMETYPE_TEXT).entity("Bad request: Column to check incorrectly specified." + CRLF)
629 .build();
630 }
631
632 if (LOG.isDebugEnabled()) {
633 LOG.debug("CHECK-AND-DELETE " + delete.toString() + ", returns "
634 + retValue);
635 }
636
637 if (!retValue) {
638 servlet.getMetrics().incrementFailedDeleteRequests(1);
639 return Response.status(Response.Status.NOT_MODIFIED)
640 .type(MIMETYPE_TEXT).entity(" Delete check failed." + CRLF)
641 .build();
642 }
643 table.flushCommits();
644 ResponseBuilder response = Response.ok();
645 servlet.getMetrics().incrementSucessfulDeleteRequests(1);
646 return response.build();
647 } catch (Exception e) {
648 servlet.getMetrics().incrementFailedDeleteRequests(1);
649 return processException(e);
650 } finally {
651 if (table != null) try {
652 table.close();
653 } catch (IOException ioe) {
654 LOG.debug("Exception received while closing the table", ioe);
655 }
656 }
657 }
658 }