001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.rest;
019
020import java.io.IOException;
021import java.util.Base64;
022import org.apache.hadoop.hbase.Cell;
023import org.apache.hadoop.hbase.CellUtil;
024import org.apache.hadoop.hbase.TableNotFoundException;
025import org.apache.hadoop.hbase.rest.model.CellModel;
026import org.apache.hadoop.hbase.rest.model.CellSetModel;
027import org.apache.hadoop.hbase.rest.model.RowModel;
028import org.apache.hadoop.hbase.util.Bytes;
029import org.apache.yetus.audience.InterfaceAudience;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.apache.hbase.thirdparty.javax.ws.rs.DELETE;
034import org.apache.hbase.thirdparty.javax.ws.rs.GET;
035import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
036import org.apache.hbase.thirdparty.javax.ws.rs.QueryParam;
037import org.apache.hbase.thirdparty.javax.ws.rs.core.CacheControl;
038import org.apache.hbase.thirdparty.javax.ws.rs.core.Context;
039import org.apache.hbase.thirdparty.javax.ws.rs.core.Response;
040import org.apache.hbase.thirdparty.javax.ws.rs.core.Response.ResponseBuilder;
041import org.apache.hbase.thirdparty.javax.ws.rs.core.UriInfo;
042
043@InterfaceAudience.Private
044public class ScannerInstanceResource extends ResourceBase {
045  private static final Logger LOG = LoggerFactory.getLogger(ScannerInstanceResource.class);
046
047  static CacheControl cacheControl;
048  static {
049    cacheControl = new CacheControl();
050    cacheControl.setNoCache(true);
051    cacheControl.setNoTransform(false);
052  }
053
054  ResultGenerator generator = null;
055  String id = null;
056  int batch = 1;
057
058  public ScannerInstanceResource() throws IOException {
059  }
060
061  public ScannerInstanceResource(String table, String id, ResultGenerator generator, int batch)
062    throws IOException {
063    this.id = id;
064    this.generator = generator;
065    this.batch = batch;
066  }
067
068  @GET
069  @Produces({ MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, MIMETYPE_PROTOBUF_IETF })
070  public Response get(final @Context UriInfo uriInfo, @QueryParam("n") int maxRows,
071    final @QueryParam("c") int maxValues) {
072    if (LOG.isTraceEnabled()) {
073      LOG.trace("GET " + uriInfo.getAbsolutePath());
074    }
075    servlet.getMetrics().incrementRequests(1);
076    if (generator == null) {
077      servlet.getMetrics().incrementFailedGetRequests(1);
078      return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT)
079        .entity("Not found" + CRLF).build();
080    } else {
081      // Updated the connection access time for each client next() call
082      RESTServlet.getInstance().getConnectionCache().updateConnectionAccessTime();
083    }
084    CellSetModel model = new CellSetModel();
085    RowModel rowModel = null;
086    byte[] rowKey = null;
087    int limit = batch;
088    if (maxValues > 0) {
089      limit = maxValues;
090    }
091    int count = limit;
092    do {
093      Cell value = null;
094      try {
095        value = generator.next();
096      } catch (IllegalStateException e) {
097        if (ScannerResource.delete(id)) {
098          servlet.getMetrics().incrementSucessfulDeleteRequests(1);
099        } else {
100          servlet.getMetrics().incrementFailedDeleteRequests(1);
101        }
102        servlet.getMetrics().incrementFailedGetRequests(1);
103        return Response.status(Response.Status.GONE).type(MIMETYPE_TEXT).entity("Gone" + CRLF)
104          .build();
105      } catch (IllegalArgumentException e) {
106        Throwable t = e.getCause();
107        if (t instanceof TableNotFoundException) {
108          return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT)
109            .entity("Not found" + CRLF).build();
110        }
111        throw e;
112      }
113      if (value == null) {
114        if (LOG.isTraceEnabled()) {
115          LOG.trace("generator exhausted");
116        }
117        // respond with 204 (No Content) if an empty cell set would be
118        // returned
119        if (count == limit) {
120          return Response.noContent().build();
121        }
122        break;
123      }
124      if (rowKey == null) {
125        rowKey = CellUtil.cloneRow(value);
126        rowModel = new RowModel(rowKey);
127      }
128      if (!Bytes.equals(CellUtil.cloneRow(value), rowKey)) {
129        // if maxRows was given as a query param, stop if we would exceed the
130        // specified number of rows
131        if (maxRows > 0) {
132          if (--maxRows == 0) {
133            generator.putBack(value);
134            break;
135          }
136        }
137        model.addRow(rowModel);
138        rowKey = CellUtil.cloneRow(value);
139        rowModel = new RowModel(rowKey);
140      }
141      rowModel.addCell(new CellModel(CellUtil.cloneFamily(value), CellUtil.cloneQualifier(value),
142        value.getTimestamp(), CellUtil.cloneValue(value)));
143    } while (--count > 0);
144    model.addRow(rowModel);
145    ResponseBuilder response = Response.ok(model);
146    response.cacheControl(cacheControl);
147    servlet.getMetrics().incrementSucessfulGetRequests(1);
148    return response.build();
149  }
150
151  @GET
152  @Produces(MIMETYPE_BINARY)
153  public Response getBinary(final @Context UriInfo uriInfo) {
154    if (LOG.isTraceEnabled()) {
155      LOG.trace("GET " + uriInfo.getAbsolutePath() + " as " + MIMETYPE_BINARY);
156    }
157    servlet.getMetrics().incrementRequests(1);
158    try {
159      Cell value = generator.next();
160      if (value == null) {
161        if (LOG.isTraceEnabled()) {
162          LOG.trace("generator exhausted");
163        }
164        return Response.noContent().build();
165      }
166      ResponseBuilder response = Response.ok(CellUtil.cloneValue(value));
167      response.cacheControl(cacheControl);
168      response.header("X-Row",
169        Bytes.toString(Base64.getEncoder().encode(CellUtil.cloneRow(value))));
170      response.header("X-Column", Bytes.toString(Base64.getEncoder()
171        .encode(CellUtil.makeColumn(CellUtil.cloneFamily(value), CellUtil.cloneQualifier(value)))));
172      response.header("X-Timestamp", value.getTimestamp());
173      servlet.getMetrics().incrementSucessfulGetRequests(1);
174      return response.build();
175    } catch (IllegalStateException e) {
176      if (ScannerResource.delete(id)) {
177        servlet.getMetrics().incrementSucessfulDeleteRequests(1);
178      } else {
179        servlet.getMetrics().incrementFailedDeleteRequests(1);
180      }
181      servlet.getMetrics().incrementFailedGetRequests(1);
182      return Response.status(Response.Status.GONE).type(MIMETYPE_TEXT).entity("Gone" + CRLF)
183        .build();
184    }
185  }
186
187  @DELETE
188  public Response delete(final @Context UriInfo uriInfo) {
189    if (LOG.isTraceEnabled()) {
190      LOG.trace("DELETE " + uriInfo.getAbsolutePath());
191    }
192    servlet.getMetrics().incrementRequests(1);
193    if (servlet.isReadOnly()) {
194      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
195        .entity("Forbidden" + CRLF).build();
196    }
197    if (ScannerResource.delete(id)) {
198      servlet.getMetrics().incrementSucessfulDeleteRequests(1);
199    } else {
200      servlet.getMetrics().incrementFailedDeleteRequests(1);
201    }
202    return Response.ok().build();
203  }
204}