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