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