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