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