/*
* Copyright © 2014-2015 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.explore.executor;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.explore.service.ExploreException;
import co.cask.cdap.explore.service.ExploreService;
import co.cask.cdap.explore.service.HandleNotFoundException;
import co.cask.cdap.proto.ColumnDesc;
import co.cask.cdap.proto.QueryHandle;
import co.cask.cdap.proto.QueryResult;
import co.cask.cdap.proto.QueryStatus;
import co.cask.http.ChunkResponder;
import co.cask.http.HttpResponder;
import com.google.common.collect.Lists;
import com.google.common.io.Closeables;
import com.google.inject.Inject;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
*
*/
@Path(Constants.Gateway.API_VERSION_3)
public class QueryExecutorHttpHandler extends AbstractQueryExecutorHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(QueryExecutorHttpHandler.class);
private final ExploreService exploreService;
@Inject
QueryExecutorHttpHandler(ExploreService exploreService) {
this.exploreService = exploreService;
}
@DELETE
@Path("data/explore/queries/{id}")
public void closeQuery(HttpRequest request, HttpResponder responder,
@PathParam("id") String id) throws ExploreException {
try {
QueryHandle handle = QueryHandle.fromId(id);
if (!handle.equals(QueryHandle.NO_OP)) {
exploreService.close(handle);
}
responder.sendStatus(HttpResponseStatus.OK);
} catch (IllegalArgumentException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
} catch (HandleNotFoundException e) {
responder.sendStatus(HttpResponseStatus.NOT_FOUND);
}
}
@GET
@Path("data/explore/queries/{id}/status")
public void getQueryStatus(HttpRequest request, HttpResponder responder,
@PathParam("id") String id) throws ExploreException {
try {
QueryHandle handle = QueryHandle.fromId(id);
QueryStatus status;
if (!handle.equals(QueryHandle.NO_OP)) {
status = exploreService.getStatus(handle);
} else {
status = QueryStatus.NO_OP;
}
responder.sendJson(HttpResponseStatus.OK, status);
} catch (IllegalArgumentException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
} catch (SQLException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST,
String.format("[SQLState %s] %s", e.getSQLState(), e.getMessage()));
} catch (HandleNotFoundException e) {
responder.sendStatus(HttpResponseStatus.NOT_FOUND);
}
}
@GET
@Path("data/explore/queries/{id}/schema")
public void getQueryResultsSchema(HttpRequest request, HttpResponder responder,
@PathParam("id") String id) throws ExploreException {
try {
QueryHandle handle = QueryHandle.fromId(id);
List<ColumnDesc> schema;
if (!handle.equals(QueryHandle.NO_OP)) {
schema = exploreService.getResultSchema(handle);
} else {
schema = Lists.newArrayList();
}
responder.sendJson(HttpResponseStatus.OK, schema);
} catch (IllegalArgumentException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
} catch (SQLException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST,
String.format("[SQLState %s] %s", e.getSQLState(), e.getMessage()));
} catch (HandleNotFoundException e) {
responder.sendStatus(HttpResponseStatus.NOT_FOUND);
}
}
@POST
@Path("data/explore/queries/{id}/next")
public void getQueryNextResults(HttpRequest request, HttpResponder responder,
@PathParam("id") String id) throws IOException, ExploreException {
// NOTE: this call is a POST because it is not idempotent: cursor of results is moved
try {
QueryHandle handle = QueryHandle.fromId(id);
List<QueryResult> results;
if (handle.equals(QueryHandle.NO_OP)) {
results = Lists.newArrayList();
} else {
Map<String, String> args = decodeArguments(request);
int size = args.containsKey("size") ? Integer.valueOf(args.get("size")) : 100;
results = exploreService.nextResults(handle, size);
}
responder.sendJson(HttpResponseStatus.OK, results);
} catch (IllegalArgumentException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
} catch (SQLException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST,
String.format("[SQLState %s] %s", e.getSQLState(), e.getMessage()));
} catch (HandleNotFoundException e) {
responder.sendStatus(HttpResponseStatus.NOT_FOUND);
}
}
@POST
@Path("data/explore/queries/{id}/preview")
public void getQueryResultPreview(HttpRequest request, HttpResponder responder,
@PathParam("id") String id) throws ExploreException {
// NOTE: this call is a POST because it is not idempotent: cursor of results is moved
try {
QueryHandle handle = QueryHandle.fromId(id);
List<QueryResult> results;
if (handle.equals(QueryHandle.NO_OP)) {
results = Lists.newArrayList();
} else {
results = exploreService.previewResults(handle);
}
responder.sendJson(HttpResponseStatus.OK, results);
} catch (IllegalArgumentException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
} catch (SQLException e) {
LOG.debug("Got exception:", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST,
String.format("[SQLState %s] %s", e.getSQLState(), e.getMessage()));
} catch (HandleNotFoundException e) {
if (e.isInactive()) {
responder.sendString(HttpResponseStatus.CONFLICT, "Preview is unavailable for inactive queries.");
return;
}
responder.sendStatus(HttpResponseStatus.NOT_FOUND);
}
}
@POST
@Path("data/explore/queries/{id}/download")
public void downloadQueryResults(HttpRequest request, HttpResponder responder,
@PathParam("id") final String id) throws ExploreException, IOException {
// NOTE: this call is a POST because it is not idempotent: cursor of results is moved
boolean responseStarted = false;
try {
QueryHandle handle = QueryHandle.fromId(id);
if (handle.equals(QueryHandle.NO_OP) ||
!exploreService.getStatus(handle).getStatus().equals(QueryStatus.OpStatus.FINISHED)) {
responder.sendStatus(HttpResponseStatus.CONFLICT);
return;
}
StringBuffer sb = new StringBuffer();
sb.append(getCSVHeaders(exploreService.getResultSchema(handle)));
sb.append('\n');
List<QueryResult> results;
results = exploreService.previewResults(handle);
if (results.isEmpty()) {
results = exploreService.nextResults(handle, DOWNLOAD_FETCH_CHUNK_SIZE);
}
ChunkResponder chunkResponder = responder.sendChunkStart(HttpResponseStatus.OK, null);
responseStarted = true;
while (!results.isEmpty()) {
for (QueryResult result : results) {
appendCSVRow(sb, result);
sb.append('\n');
}
// If failed to send to client, just propagate the IOException and let netty-http to handle
chunkResponder.sendChunk(ChannelBuffers.wrappedBuffer(sb.toString().getBytes("UTF-8")));
sb.delete(0, sb.length());
results = exploreService.nextResults(handle, DOWNLOAD_FETCH_CHUNK_SIZE);
}
Closeables.closeQuietly(chunkResponder);
} catch (IllegalArgumentException e) {
LOG.debug("Got exception:", e);
// We can't send another response if sendChunkStart has been called
if (!responseStarted) {
responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
}
} catch (SQLException e) {
LOG.debug("Got exception:", e);
if (!responseStarted) {
responder.sendString(HttpResponseStatus.BAD_REQUEST, String.format("[SQLState %s] %s",
e.getSQLState(), e.getMessage()));
}
} catch (HandleNotFoundException e) {
if (!responseStarted) {
if (e.isInactive()) {
responder.sendString(HttpResponseStatus.CONFLICT, "Query is inactive");
} else {
responder.sendStatus(HttpResponseStatus.NOT_FOUND);
}
}
}
}
}