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.net.URLDecoder;
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.WebApplicationException;
34  import javax.ws.rs.core.CacheControl;
35  import javax.ws.rs.core.Context;
36  import javax.ws.rs.core.HttpHeaders;
37  import javax.ws.rs.core.Response;
38  import javax.ws.rs.core.UriInfo;
39  import javax.ws.rs.core.Response.ResponseBuilder;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.hadoop.hbase.HConstants;
44  import org.apache.hadoop.hbase.KeyValue;
45  import org.apache.hadoop.hbase.client.Delete;
46  import org.apache.hadoop.hbase.client.HTableInterface;
47  import org.apache.hadoop.hbase.client.HTablePool;
48  import org.apache.hadoop.hbase.client.HTable;
49  import org.apache.hadoop.hbase.client.Put;
50  import org.apache.hadoop.hbase.rest.model.CellModel;
51  import org.apache.hadoop.hbase.rest.model.CellSetModel;
52  import org.apache.hadoop.hbase.rest.model.RowModel;
53  import org.apache.hadoop.hbase.util.Bytes;
54  
55  public class RowResource extends ResourceBase {
56    private static final Log LOG = LogFactory.getLog(RowResource.class);
57  
58    String tableName;
59    RowSpec rowspec;
60    CacheControl cacheControl;
61  
62    /**
63     * Constructor
64     * @param table
65     * @param rowspec
66     * @param versions
67     * @throws IOException
68     */
69    public RowResource(String table, String rowspec, String versions)
70        throws IOException {
71      super();
72      this.tableName = table;
73      this.rowspec = new RowSpec(URLDecoder.decode(rowspec,
74        HConstants.UTF8_ENCODING));
75      if (versions != null) {
76        this.rowspec.setMaxVersions(Integer.valueOf(versions));
77      }
78      this.cacheControl = new CacheControl();
79      this.cacheControl.setMaxAge(servlet.getMaxAge(tableName));
80      this.cacheControl.setNoTransform(false);    
81    }
82  
83    @GET
84    @Produces({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF})
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(tableName, rowspec, null);
93        if (!generator.hasNext()) {
94          throw new WebApplicationException(Response.Status.NOT_FOUND);
95        }
96        int count = 0;
97        CellSetModel model = new CellSetModel();
98        KeyValue value = generator.next();
99        byte[] rowKey = value.getRow();
100       RowModel rowModel = new RowModel(rowKey);
101       do {
102         if (!Bytes.equals(value.getRow(), rowKey)) {
103           model.addRow(rowModel);
104           rowKey = value.getRow();
105           rowModel = new RowModel(rowKey);
106         }
107         rowModel.addCell(
108           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       ResponseBuilder response = Response.ok(model);
117       response.cacheControl(cacheControl);
118       return response.build();
119     } catch (IOException e) {
120       throw new WebApplicationException(e,
121                   Response.Status.SERVICE_UNAVAILABLE);
122     }
123   }
124 
125   @GET
126   @Produces(MIMETYPE_BINARY)
127   public Response getBinary(final @Context UriInfo uriInfo) {
128     if (LOG.isDebugEnabled()) {
129       LOG.debug("GET " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
130     }
131     servlet.getMetrics().incrementRequests(1);
132     // doesn't make sense to use a non specific coordinate as this can only
133     // return a single cell
134     if (!rowspec.hasColumns() || rowspec.getColumns().length > 1) {
135       throw new WebApplicationException(Response.Status.BAD_REQUEST);
136     }
137     try {
138       ResultGenerator generator =
139         ResultGenerator.fromRowSpec(tableName, rowspec, null);
140       if (!generator.hasNext()) {
141         throw new WebApplicationException(Response.Status.NOT_FOUND);
142       }
143       KeyValue value = generator.next();
144       ResponseBuilder response = Response.ok(value.getValue());
145       response.cacheControl(cacheControl);
146       response.header("X-Timestamp", value.getTimestamp());
147       return response.build();
148     } catch (IOException e) {
149       throw new WebApplicationException(e,
150                   Response.Status.SERVICE_UNAVAILABLE);
151     }
152   }
153 
154   Response update(final CellSetModel model, final boolean replace) {
155     servlet.getMetrics().incrementRequests(1);
156     HTablePool pool = servlet.getTablePool();
157     HTableInterface table = null;
158     try {
159       List<RowModel> rows = model.getRows();
160       table = pool.getTable(tableName);
161       ((HTable)table).setAutoFlush(false);
162       for (RowModel row: rows) {
163         byte[] key = row.getKey();
164         Put put = new Put(key);
165         for (CellModel cell: row.getCells()) {
166           byte [][] parts = KeyValue.parseColumn(cell.getColumn());
167           if (parts.length == 2 && parts[1].length > 0) {
168             put.add(parts[0], parts[1], cell.getTimestamp(), cell.getValue());
169           } else {
170             put.add(parts[0], null, cell.getTimestamp(), cell.getValue());
171           }
172         }
173         table.put(put);
174         if (LOG.isDebugEnabled()) {
175           LOG.debug("PUT " + put.toString());
176         }
177       }
178       ((HTable)table).setAutoFlush(true);
179       table.flushCommits();
180       ResponseBuilder response = Response.ok();
181       return response.build();
182     } catch (IOException e) {
183       throw new WebApplicationException(e,
184                   Response.Status.SERVICE_UNAVAILABLE);
185     } finally {
186       if (table != null) {
187         pool.putTable(table);
188       }
189     }
190   }
191 
192   // This currently supports only update of one row at a time.
193   Response updateBinary(final byte[] message, final HttpHeaders headers,
194       final boolean replace) {
195     servlet.getMetrics().incrementRequests(1);
196     HTablePool pool = servlet.getTablePool();
197     HTableInterface table = null;    
198     try {
199       byte[] row = rowspec.getRow();
200       byte[][] columns = rowspec.getColumns();
201       byte[] column = null;
202       if (columns != null) {
203         column = columns[0];
204       }
205       long timestamp = HConstants.LATEST_TIMESTAMP;
206       List<String> vals = headers.getRequestHeader("X-Row");
207       if (vals != null && !vals.isEmpty()) {
208         row = Bytes.toBytes(vals.get(0));
209       }
210       vals = headers.getRequestHeader("X-Column");
211       if (vals != null && !vals.isEmpty()) {
212         column = Bytes.toBytes(vals.get(0));
213       }
214       vals = headers.getRequestHeader("X-Timestamp");
215       if (vals != null && !vals.isEmpty()) {
216         timestamp = Long.valueOf(vals.get(0));
217       }
218       if (column == null) {
219         throw new WebApplicationException(Response.Status.BAD_REQUEST);
220       }
221       Put put = new Put(row);
222       byte parts[][] = KeyValue.parseColumn(column);
223       if (parts.length == 2 && parts[1].length > 0) {
224         put.add(parts[0], parts[1], timestamp, message);
225       } else {
226         put.add(parts[0], null, timestamp, message);
227       }
228       table = pool.getTable(tableName);
229       table.put(put);
230       if (LOG.isDebugEnabled()) {
231         LOG.debug("PUT " + put.toString());
232       }
233       return Response.ok().build();
234     } catch (IOException e) {
235       throw new WebApplicationException(e,
236                   Response.Status.SERVICE_UNAVAILABLE);
237     } finally {
238       if (table != null) {
239         pool.putTable(table);
240       }
241     }
242   }
243 
244   @PUT
245   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF})
246   public Response put(final CellSetModel model,
247       final @Context UriInfo uriInfo) {
248     if (LOG.isDebugEnabled()) {
249       LOG.debug("PUT " + uriInfo.getAbsolutePath());
250     }
251     return update(model, true);
252   }
253 
254   @PUT
255   @Consumes(MIMETYPE_BINARY)
256   public Response putBinary(final byte[] message,
257       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
258     if (LOG.isDebugEnabled()) {
259       LOG.debug("PUT " + uriInfo.getAbsolutePath() + " as "+ MIMETYPE_BINARY);
260     }
261     return updateBinary(message, headers, true);
262   }
263 
264   @POST
265   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF})
266   public Response post(final CellSetModel model,
267       final @Context UriInfo uriInfo) {
268     if (LOG.isDebugEnabled()) {
269       LOG.debug("POST " + uriInfo.getAbsolutePath());
270     }
271     return update(model, false);
272   }
273 
274   @POST
275   @Consumes(MIMETYPE_BINARY)
276   public Response postBinary(final byte[] message,
277       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
278     if (LOG.isDebugEnabled()) {
279       LOG.debug("POST " + uriInfo.getAbsolutePath() + " as "+MIMETYPE_BINARY);
280     }
281     return updateBinary(message, headers, false);
282   }
283 
284   @DELETE
285   public Response delete(final @Context UriInfo uriInfo) {
286     if (LOG.isDebugEnabled()) {
287       LOG.debug("DELETE " + uriInfo.getAbsolutePath());
288     }
289     servlet.getMetrics().incrementRequests(1);
290     Delete delete = null;
291     if (rowspec.hasTimestamp())
292       delete = new Delete(rowspec.getRow(), rowspec.getTimestamp(), null);
293     else
294       delete = new Delete(rowspec.getRow());
295 
296     for (byte[] column: rowspec.getColumns()) {
297       byte[][] split = KeyValue.parseColumn(column);
298       if (rowspec.hasTimestamp()) {
299         if (split.length == 2 && split[1].length != 0) {
300           delete.deleteColumns(split[0], split[1], rowspec.getTimestamp());
301         } else {
302           delete.deleteFamily(split[0], rowspec.getTimestamp());
303         }
304       } else {
305         if (split.length == 2 && split[1].length != 0) {
306           delete.deleteColumns(split[0], split[1]);
307         } else {
308           delete.deleteFamily(split[0]);
309         }
310       }
311     }
312     HTablePool pool = servlet.getTablePool();
313     HTableInterface table = null;
314     try {
315       table = pool.getTable(tableName);
316       table.delete(delete);
317       if (LOG.isDebugEnabled()) {
318         LOG.debug("DELETE " + delete.toString());
319       }
320     } catch (IOException e) {
321       throw new WebApplicationException(e, 
322                   Response.Status.SERVICE_UNAVAILABLE);
323     } finally {
324       if (table != null) {
325         pool.putTable(table);
326       }
327     }
328     return Response.ok().build();
329   }
330 }