co.cask.cdap.explore.executor.ExploreExecutorHttpHandler.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.explore.executor.ExploreExecutorHttpHandler.java

Source

/*
 * 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.api.data.format.FormatSpecification;
import co.cask.cdap.api.data.schema.Schema;
import co.cask.cdap.api.data.schema.UnsupportedTypeException;
import co.cask.cdap.api.dataset.Dataset;
import co.cask.cdap.api.dataset.DatasetManagementException;
import co.cask.cdap.api.dataset.DatasetSpecification;
import co.cask.cdap.api.dataset.lib.PartitionKey;
import co.cask.cdap.api.dataset.lib.PartitionedFileSet;
import co.cask.cdap.api.dataset.lib.PartitionedFileSetArguments;
import co.cask.cdap.api.dataset.lib.Partitioning;
import co.cask.cdap.common.BadRequestException;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.data.dataset.SystemDatasetInstantiator;
import co.cask.cdap.data.dataset.SystemDatasetInstantiatorFactory;
import co.cask.cdap.data2.dataset2.DatasetFramework;
import co.cask.cdap.data2.transaction.stream.StreamAdmin;
import co.cask.cdap.explore.service.ExploreException;
import co.cask.cdap.explore.service.ExploreTableManager;
import co.cask.cdap.internal.io.SchemaTypeAdapter;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.QueryHandle;
import co.cask.http.AbstractHttpHandler;
import co.cask.http.HttpResponder;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import org.jboss.netty.buffer.ChannelBufferInputStream;
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.io.InputStreamReader;
import java.io.Reader;
import java.sql.SQLException;
import java.util.Map;
import javax.annotation.Nullable;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

/**
 * Handler that implements internal explore APIs.
 */
@Path(Constants.Gateway.API_VERSION_3 + "/namespaces/{namespace-id}/data/explore")
public class ExploreExecutorHttpHandler extends AbstractHttpHandler {
    private static final Logger LOG = LoggerFactory.getLogger(ExploreExecutorHttpHandler.class);
    private static final Gson GSON = new GsonBuilder().registerTypeAdapter(Schema.class, new SchemaTypeAdapter())
            .create();

    private final ExploreTableManager exploreTableManager;
    private final DatasetFramework datasetFramework;
    private final StreamAdmin streamAdmin;
    private final SystemDatasetInstantiatorFactory datasetInstantiatorFactory;

    @Inject
    public ExploreExecutorHttpHandler(ExploreTableManager exploreTableManager, DatasetFramework datasetFramework,
            StreamAdmin streamAdmin, SystemDatasetInstantiatorFactory datasetInstantiatorFactory) {
        this.exploreTableManager = exploreTableManager;
        this.datasetFramework = datasetFramework;
        this.streamAdmin = streamAdmin;
        this.datasetInstantiatorFactory = datasetInstantiatorFactory;
    }

    @POST
    @Path("streams/{stream}/tables/{table}/enable")
    public void enableStream(HttpRequest request, HttpResponder responder,
            @PathParam("namespace-id") String namespaceId, @PathParam("stream") String streamName,
            @PathParam("table") String tableName) throws Exception {
        Id.Stream streamId = Id.Stream.from(namespaceId, streamName);
        try (Reader reader = new InputStreamReader(new ChannelBufferInputStream(request.getContent()))) {
            FormatSpecification format = GSON.fromJson(reader, FormatSpecification.class);
            if (format == null) {
                throw new BadRequestException("Expected format in the body");
            }
            QueryHandle handle = exploreTableManager.enableStream(tableName, streamId, format);
            JsonObject json = new JsonObject();
            json.addProperty("handle", handle.getHandle());
            responder.sendJson(HttpResponseStatus.OK, json);
        } catch (UnsupportedTypeException e) {
            LOG.error("Exception while generating create statement for stream {}", streamName, e);
            responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
        }
    }

    @POST
    @Path("streams/{stream}/tables/{table}/disable")
    public void disableStream(HttpRequest request, HttpResponder responder,
            @PathParam("namespace-id") String namespaceId, @PathParam("stream") String streamName,
            @PathParam("table") String tableName) {

        Id.Stream streamId = Id.Stream.from(namespaceId, streamName);
        try {
            // throws io exception if there is no stream
            streamAdmin.getConfig(streamId);
        } catch (IOException e) {
            LOG.debug("Could not find stream {} to disable explore on.", streamName, e);
            responder.sendString(HttpResponseStatus.NOT_FOUND, "Could not find stream " + streamName);
            return;
        }

        try {
            QueryHandle handle = exploreTableManager.disableStream(tableName, streamId);
            JsonObject json = new JsonObject();
            json.addProperty("handle", handle.getHandle());
            responder.sendJson(HttpResponseStatus.OK, json);
        } catch (Throwable t) {
            LOG.error("Got exception disabling exploration for stream {}", streamId, t);
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR, t.getMessage());
        }
    }

    /**
     * Enable ad-hoc exploration of a dataset instance.
     */
    @POST
    @Path("datasets/{dataset}/enable")
    public void enableDataset(HttpRequest request, HttpResponder responder,
            @PathParam("namespace-id") String namespaceId, @PathParam("dataset") String datasetName) {
        Id.DatasetInstance datasetID = Id.DatasetInstance.from(namespaceId, datasetName);
        DatasetSpecification datasetSpec;
        try {
            datasetSpec = datasetFramework.getDatasetSpec(datasetID);
            if (datasetSpec == null) {
                responder.sendString(HttpResponseStatus.NOT_FOUND, "Cannot load dataset " + datasetID);
                return;
            }
        } catch (DatasetManagementException e) {
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR,
                    "Error getting spec for dataset " + datasetID);
            return;
        }

        try {
            QueryHandle handle = exploreTableManager.enableDataset(datasetID, datasetSpec);
            JsonObject json = new JsonObject();
            json.addProperty("handle", handle.getHandle());
            responder.sendJson(HttpResponseStatus.OK, json);
        } catch (IllegalArgumentException e) {
            responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
        } catch (ExploreException e) {
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR,
                    "Error enabling explore on dataset " + datasetID);
        } catch (SQLException e) {
            responder.sendString(HttpResponseStatus.BAD_REQUEST,
                    "SQL exception while trying to enable explore on dataset " + datasetID);
        } catch (UnsupportedTypeException e) {
            responder.sendString(HttpResponseStatus.BAD_REQUEST,
                    "Schema for dataset " + datasetID + " is not supported for exploration: " + e.getMessage());
        } catch (Throwable e) {
            LOG.error("Got exception:", e);
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    /**
     * Disable ad-hoc exploration of a dataset instance.
     */
    @POST
    @Path("datasets/{dataset}/disable")
    public void disableDataset(HttpRequest request, HttpResponder responder,
            @PathParam("namespace-id") String namespaceId, @PathParam("dataset") String datasetName) {

        LOG.debug("Disabling explore for dataset instance {}", datasetName);
        Id.DatasetInstance datasetID = Id.DatasetInstance.from(namespaceId, datasetName);
        DatasetSpecification spec;
        try {
            spec = datasetFramework.getDatasetSpec(datasetID);
            if (spec == null) {
                responder.sendString(HttpResponseStatus.NOT_FOUND, "Cannot load dataset " + datasetID);
                return;
            }
        } catch (DatasetManagementException e) {
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR,
                    "Error getting spec for dataset " + datasetID);
            return;
        }

        try {
            QueryHandle handle = exploreTableManager.disableDataset(datasetID, spec);
            JsonObject json = new JsonObject();
            json.addProperty("handle", handle.getHandle());
            responder.sendJson(HttpResponseStatus.OK, json);
        } catch (Throwable e) {
            LOG.error("Got exception while trying to disable explore on dataset {}", datasetID, e);
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    @POST
    @Path("datasets/{dataset}/partitions")
    public void addPartition(HttpRequest request, HttpResponder responder,
            @PathParam("namespace-id") String namespaceId, @PathParam("dataset") String datasetName) {
        Id.DatasetInstance datasetInstanceId = Id.DatasetInstance.from(namespaceId, datasetName);
        Dataset dataset;
        try (SystemDatasetInstantiator datasetInstantiator = datasetInstantiatorFactory.create()) {
            dataset = datasetInstantiator.getDataset(datasetInstanceId);

            if (dataset == null) {
                responder.sendString(HttpResponseStatus.NOT_FOUND, "Cannot load dataset " + datasetInstanceId);
                return;
            }
        } catch (IOException e) {
            String classNotFoundMessage = isClassNotFoundException(e);
            if (classNotFoundMessage != null) {
                JsonObject json = new JsonObject();
                json.addProperty("handle", QueryHandle.NO_OP.getHandle());
                responder.sendJson(HttpResponseStatus.OK, json);
                return;
            }
            LOG.error("Exception instantiating dataset {}.", datasetInstanceId, e);
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR,
                    "Exception instantiating dataset " + datasetName);
            return;
        }

        try {
            if (!(dataset instanceof PartitionedFileSet)) {
                responder.sendString(HttpResponseStatus.BAD_REQUEST, "not a partitioned dataset.");
                return;
            }
            Partitioning partitioning = ((PartitionedFileSet) dataset).getPartitioning();

            Reader reader = new InputStreamReader(new ChannelBufferInputStream(request.getContent()));
            Map<String, String> properties = GSON.fromJson(reader, new TypeToken<Map<String, String>>() {
            }.getType());
            String fsPath = properties.get("path");
            if (fsPath == null) {
                responder.sendString(HttpResponseStatus.BAD_REQUEST, "path was not specified.");
                return;
            }

            PartitionKey partitionKey;
            try {
                partitionKey = PartitionedFileSetArguments.getOutputPartitionKey(properties, partitioning);
            } catch (Exception e) {
                responder.sendString(HttpResponseStatus.BAD_REQUEST, "invalid partition key: " + e.getMessage());
                return;
            }
            if (partitionKey == null) {
                responder.sendString(HttpResponseStatus.BAD_REQUEST, "no partition key was given.");
                return;
            }

            QueryHandle handle = exploreTableManager.addPartition(datasetInstanceId, partitionKey, fsPath);
            JsonObject json = new JsonObject();
            json.addProperty("handle", handle.getHandle());
            responder.sendJson(HttpResponseStatus.OK, json);
        } catch (Throwable e) {
            LOG.error("Got exception:", e);
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    // this should really be a DELETE request. However, the partition key must be passed in the body
    // of the request, and that does not work with many HTTP clients, including Java's URLConnection.
    @POST
    @Path("datasets/{dataset}/deletePartition")
    public void dropPartition(HttpRequest request, HttpResponder responder,
            @PathParam("namespace-id") String namespaceId, @PathParam("dataset") String datasetName) {
        Id.DatasetInstance datasetInstanceId = Id.DatasetInstance.from(namespaceId, datasetName);
        Dataset dataset;
        try (SystemDatasetInstantiator datasetInstantiator = datasetInstantiatorFactory.create()) {

            dataset = datasetInstantiator.getDataset(datasetInstanceId);
            if (dataset == null) {
                responder.sendString(HttpResponseStatus.NOT_FOUND, "Cannot load dataset " + datasetInstanceId);
                return;
            }
        } catch (IOException e) {
            String classNotFoundMessage = isClassNotFoundException(e);
            if (classNotFoundMessage != null) {
                JsonObject json = new JsonObject();
                json.addProperty("handle", QueryHandle.NO_OP.getHandle());
                responder.sendJson(HttpResponseStatus.OK, json);
                return;
            }
            LOG.error("Exception instantiating dataset {}.", datasetInstanceId, e);
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR,
                    "Exception instantiating dataset " + datasetInstanceId);
            return;
        }

        try {
            if (!(dataset instanceof PartitionedFileSet)) {
                responder.sendString(HttpResponseStatus.BAD_REQUEST, "not a partitioned dataset.");
                return;
            }
            Partitioning partitioning = ((PartitionedFileSet) dataset).getPartitioning();

            Reader reader = new InputStreamReader(new ChannelBufferInputStream(request.getContent()));
            Map<String, String> properties = GSON.fromJson(reader, new TypeToken<Map<String, String>>() {
            }.getType());

            PartitionKey partitionKey;
            try {
                partitionKey = PartitionedFileSetArguments.getOutputPartitionKey(properties, partitioning);
            } catch (Exception e) {
                responder.sendString(HttpResponseStatus.BAD_REQUEST, "invalid partition key: " + e.getMessage());
                return;
            }
            if (partitionKey == null) {
                responder.sendString(HttpResponseStatus.BAD_REQUEST, "no partition key was given.");
                return;
            }

            QueryHandle handle = exploreTableManager.dropPartition(datasetInstanceId, partitionKey);
            JsonObject json = new JsonObject();
            json.addProperty("handle", handle.getHandle());
            responder.sendJson(HttpResponseStatus.OK, json);
        } catch (Throwable e) {
            LOG.error("Got exception:", e);
            responder.sendString(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    // returns the cause of the class not found exception if it is one. Otherwise returns null.
    @Nullable
    private static String isClassNotFoundException(Throwable e) {
        if (e instanceof ClassNotFoundException) {
            return e.getMessage();
        }
        if (e.getCause() != null) {
            return isClassNotFoundException(e.getCause());
        }
        return null;
    }
}