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