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