View Javadoc

1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.rest;
22  
23  import java.io.IOException;
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.WebApplicationException;
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.client.Delete;
44  import org.apache.hadoop.hbase.client.HTableInterface;
45  import org.apache.hadoop.hbase.client.HTablePool;
46  import org.apache.hadoop.hbase.client.HTable;
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.rest.transform.Transform;
52  import org.apache.hadoop.hbase.util.Bytes;
53  
54  public class RowResource extends ResourceBase {
55    private static final Log LOG = LogFactory.getLog(RowResource.class);
56  
57    TableResource tableResource;
58    RowSpec rowspec;
59  
60    /**
61     * Constructor
62     * @param tableResource
63     * @param rowspec
64     * @param versions
65     * @throws IOException
66     */
67    public RowResource(TableResource tableResource, String rowspec,
68        String versions) throws IOException {
69      super();
70      this.tableResource = tableResource;
71      this.rowspec = new RowSpec(rowspec);
72      if (versions != null) {
73        this.rowspec.setMaxVersions(Integer.valueOf(versions));
74      }
75    }
76  
77    @GET
78    @Produces({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF})
79    public Response get(final @Context UriInfo uriInfo) {
80      if (LOG.isDebugEnabled()) {
81        LOG.debug("GET " + uriInfo.getAbsolutePath());
82      }
83      servlet.getMetrics().incrementRequests(1);
84      try {
85        ResultGenerator generator =
86          ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null);
87        if (!generator.hasNext()) {
88          throw new WebApplicationException(Response.Status.NOT_FOUND);
89        }
90        int count = 0;
91        CellSetModel model = new CellSetModel();
92        KeyValue value = generator.next();
93        byte[] rowKey = value.getRow();
94        RowModel rowModel = new RowModel(rowKey);
95        do {
96          if (!Bytes.equals(value.getRow(), rowKey)) {
97            model.addRow(rowModel);
98            rowKey = value.getRow();
99            rowModel = new RowModel(rowKey);
100         }
101         byte[] family = value.getFamily();
102         byte[] qualifier = value.getQualifier();
103         byte[] data = tableResource.transform(family, qualifier,
104           value.getValue(), Transform.Direction.OUT);
105         rowModel.addCell(new CellModel(family, qualifier,
106           value.getTimestamp(), data));
107         if (++count > rowspec.getMaxValues()) {
108           break;
109         }
110         value = generator.next();
111       } while (value != null);
112       model.addRow(rowModel);
113       return Response.ok(model).build();
114     } catch (IOException e) {
115       throw new WebApplicationException(e,
116                   Response.Status.SERVICE_UNAVAILABLE);
117     }
118   }
119 
120   @GET
121   @Produces(MIMETYPE_BINARY)
122   public Response getBinary(final @Context UriInfo uriInfo) {
123     if (LOG.isDebugEnabled()) {
124       LOG.debug("GET " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
125     }
126     servlet.getMetrics().incrementRequests(1);
127     // doesn't make sense to use a non specific coordinate as this can only
128     // return a single cell
129     if (!rowspec.hasColumns() || rowspec.getColumns().length > 1) {
130       throw new WebApplicationException(Response.Status.BAD_REQUEST);
131     }
132     try {
133       ResultGenerator generator =
134         ResultGenerator.fromRowSpec(tableResource.getName(), rowspec, null);
135       if (!generator.hasNext()) {
136         throw new WebApplicationException(Response.Status.NOT_FOUND);
137       }
138       KeyValue value = generator.next();
139       byte[] family = value.getFamily();
140       byte[] qualifier = value.getQualifier();
141       byte[] data = tableResource.transform(family, qualifier,
142         value.getValue(), Transform.Direction.OUT);
143       ResponseBuilder response = Response.ok(data);
144       response.header("X-Timestamp", value.getTimestamp());
145       return response.build();
146     } catch (IOException e) {
147       throw new WebApplicationException(e,
148                   Response.Status.SERVICE_UNAVAILABLE);
149     }
150   }
151 
152   Response update(final CellSetModel model, final boolean replace) {
153     servlet.getMetrics().incrementRequests(1);
154     if (servlet.isReadOnly()) {
155       throw new WebApplicationException(Response.Status.FORBIDDEN);
156     }
157     HTablePool pool = servlet.getTablePool();
158     HTableInterface table = null;
159     try {
160       List<RowModel> rows = model.getRows();
161       table = pool.getTable(tableResource.getName());
162       ((HTable)table).setAutoFlush(false);
163       for (RowModel row: rows) {
164         byte[] key = row.getKey();
165         if (key == null) {
166           key = rowspec.getRow();
167         }
168         if (key == null) {
169           throw new WebApplicationException(Response.Status.BAD_REQUEST);
170         }
171         Put put = new Put(key);
172         int i = 0;
173         for (CellModel cell: row.getCells()) {
174           byte[] col = cell.getColumn();
175           if (col == null) try {
176             col = rowspec.getColumns()[i++];
177           } catch (ArrayIndexOutOfBoundsException e) {
178             col = null;
179           }
180           if (col == null) {
181             throw new WebApplicationException(Response.Status.BAD_REQUEST);
182           }
183           byte [][] parts = KeyValue.parseColumn(col);
184           if (parts.length == 2 && parts[1].length > 0) {
185             put.add(parts[0], parts[1], cell.getTimestamp(),
186               tableResource.transform(parts[0], parts[1], cell.getValue(),
187                 Transform.Direction.IN));
188           } else {
189             put.add(parts[0], null, cell.getTimestamp(),
190               tableResource.transform(parts[0], null, cell.getValue(),
191                 Transform.Direction.IN));
192           }
193         }
194         table.put(put);
195         if (LOG.isDebugEnabled()) {
196           LOG.debug("PUT " + put.toString());
197         }
198       }
199       ((HTable)table).setAutoFlush(true);
200       table.flushCommits();
201       ResponseBuilder response = Response.ok();
202       return response.build();
203     } catch (IOException e) {
204       throw new WebApplicationException(e,
205                   Response.Status.SERVICE_UNAVAILABLE);
206     } finally {
207       if (table != null) {
208         pool.putTable(table);
209       }
210     }
211   }
212 
213   // This currently supports only update of one row at a time.
214   Response updateBinary(final byte[] message, final HttpHeaders headers,
215       final boolean replace) {
216     servlet.getMetrics().incrementRequests(1);
217     if (servlet.isReadOnly()) {
218       throw new WebApplicationException(Response.Status.FORBIDDEN);
219     }
220     HTablePool pool = servlet.getTablePool();
221     HTableInterface table = null;
222     try {
223       byte[] row = rowspec.getRow();
224       byte[][] columns = rowspec.getColumns();
225       byte[] column = null;
226       if (columns != null) {
227         column = columns[0];
228       }
229       long timestamp = HConstants.LATEST_TIMESTAMP;
230       List<String> vals = headers.getRequestHeader("X-Row");
231       if (vals != null && !vals.isEmpty()) {
232         row = Bytes.toBytes(vals.get(0));
233       }
234       vals = headers.getRequestHeader("X-Column");
235       if (vals != null && !vals.isEmpty()) {
236         column = Bytes.toBytes(vals.get(0));
237       }
238       vals = headers.getRequestHeader("X-Timestamp");
239       if (vals != null && !vals.isEmpty()) {
240         timestamp = Long.valueOf(vals.get(0));
241       }
242       if (column == null) {
243         throw new WebApplicationException(Response.Status.BAD_REQUEST);
244       }
245       Put put = new Put(row);
246       byte parts[][] = KeyValue.parseColumn(column);
247       if (parts.length == 2 && parts[1].length > 0) {
248         put.add(parts[0], parts[1], timestamp,
249           tableResource.transform(parts[0], parts[1], message,
250             Transform.Direction.IN));
251       } else {
252         put.add(parts[0], null, timestamp,
253           tableResource.transform(parts[0], null, message,
254             Transform.Direction.IN));
255       }
256       table = pool.getTable(tableResource.getName());
257       table.put(put);
258       if (LOG.isDebugEnabled()) {
259         LOG.debug("PUT " + put.toString());
260       }
261       return Response.ok().build();
262     } catch (IOException e) {
263       throw new WebApplicationException(e,
264                   Response.Status.SERVICE_UNAVAILABLE);
265     } finally {
266       if (table != null) {
267         pool.putTable(table);
268       }
269     }
270   }
271 
272   @PUT
273   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF})
274   public Response put(final CellSetModel model,
275       final @Context UriInfo uriInfo) {
276     if (LOG.isDebugEnabled()) {
277       LOG.debug("PUT " + uriInfo.getAbsolutePath());
278     }
279     return update(model, true);
280   }
281 
282   @PUT
283   @Consumes(MIMETYPE_BINARY)
284   public Response putBinary(final byte[] message,
285       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
286     if (LOG.isDebugEnabled()) {
287       LOG.debug("PUT " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
288     }
289     return updateBinary(message, headers, true);
290   }
291 
292   @POST
293   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF})
294   public Response post(final CellSetModel model,
295       final @Context UriInfo uriInfo) {
296     if (LOG.isDebugEnabled()) {
297       LOG.debug("POST " + uriInfo.getAbsolutePath());
298     }
299     return update(model, false);
300   }
301 
302   @POST
303   @Consumes(MIMETYPE_BINARY)
304   public Response postBinary(final byte[] message,
305       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
306     if (LOG.isDebugEnabled()) {
307       LOG.debug("POST " + uriInfo.getAbsolutePath() + " as "+MIMETYPE_BINARY);
308     }
309     return updateBinary(message, headers, false);
310   }
311 
312   @DELETE
313   public Response delete(final @Context UriInfo uriInfo) {
314     if (LOG.isDebugEnabled()) {
315       LOG.debug("DELETE " + uriInfo.getAbsolutePath());
316     }
317     servlet.getMetrics().incrementRequests(1);
318     if (servlet.isReadOnly()) {
319       throw new WebApplicationException(Response.Status.FORBIDDEN);
320     }
321     Delete delete = null;
322     if (rowspec.hasTimestamp())
323       delete = new Delete(rowspec.getRow(), rowspec.getTimestamp(), null);
324     else
325       delete = new Delete(rowspec.getRow());
326 
327     for (byte[] column: rowspec.getColumns()) {
328       byte[][] split = KeyValue.parseColumn(column);
329       if (rowspec.hasTimestamp()) {
330         if (split.length == 2 && split[1].length != 0) {
331           delete.deleteColumns(split[0], split[1], rowspec.getTimestamp());
332         } else {
333           delete.deleteFamily(split[0], rowspec.getTimestamp());
334         }
335       } else {
336         if (split.length == 2 && split[1].length != 0) {
337           delete.deleteColumns(split[0], split[1]);
338         } else {
339           delete.deleteFamily(split[0]);
340         }
341       }
342     }
343     HTablePool pool = servlet.getTablePool();
344     HTableInterface table = null;
345     try {
346       table = pool.getTable(tableResource.getName());
347       table.delete(delete);
348       if (LOG.isDebugEnabled()) {
349         LOG.debug("DELETE " + delete.toString());
350       }
351     } catch (IOException e) {
352       throw new WebApplicationException(e, 
353                   Response.Status.SERVICE_UNAVAILABLE);
354     } finally {
355       if (table != null) {
356         pool.putTable(table);
357       }
358     }
359     return Response.ok().build();
360   }
361 }