org.apache.asterix.api.http.server.RestApiServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.asterix.api.http.server.RestApiServlet.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.asterix.api.http.server;

import static org.apache.asterix.api.http.server.ServletConstants.HYRACKS_CONNECTION_ATTR;
import static org.apache.asterix.api.http.server.ServletConstants.HYRACKS_DATASET_ATTR;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.asterix.app.result.ResultReader;
import org.apache.asterix.app.translator.QueryTranslator;
import org.apache.asterix.app.translator.RequestParameters;
import org.apache.asterix.common.config.GlobalConfig;
import org.apache.asterix.common.context.IStorageComponentProvider;
import org.apache.asterix.common.dataflow.ICcApplicationContext;
import org.apache.asterix.common.exceptions.AsterixException;
import org.apache.asterix.compiler.provider.ILangCompilationProvider;
import org.apache.asterix.lang.aql.parser.TokenMgrError;
import org.apache.asterix.lang.common.base.IParser;
import org.apache.asterix.lang.common.base.IParserFactory;
import org.apache.asterix.lang.common.base.Statement;
import org.apache.asterix.metadata.MetadataManager;
import org.apache.asterix.translator.IRequestParameters;
import org.apache.asterix.translator.IStatementExecutor;
import org.apache.asterix.translator.IStatementExecutor.ResultDelivery;
import org.apache.asterix.translator.IStatementExecutorFactory;
import org.apache.asterix.translator.SessionConfig;
import org.apache.asterix.translator.SessionConfig.OutputFormat;
import org.apache.asterix.translator.SessionConfig.PlanFormat;
import org.apache.asterix.translator.SessionOutput;
import org.apache.hyracks.api.client.IHyracksClientConnection;
import org.apache.hyracks.api.dataset.IHyracksDataset;
import org.apache.hyracks.client.dataset.HyracksDataset;
import org.apache.hyracks.http.api.IServletRequest;
import org.apache.hyracks.http.api.IServletResponse;
import org.apache.hyracks.http.server.AbstractServlet;
import org.apache.hyracks.http.server.utils.HttpUtil;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;

import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;

public abstract class RestApiServlet extends AbstractServlet {
    private static final Logger LOGGER = Logger.getLogger(RestApiServlet.class.getName());
    private final ICcApplicationContext appCtx;
    private final ILangCompilationProvider compilationProvider;
    private final IParserFactory parserFactory;
    private final IStatementExecutorFactory statementExecutorFactory;
    private final IStorageComponentProvider componentProvider;

    public RestApiServlet(ConcurrentMap<String, Object> ctx, String[] paths, ICcApplicationContext appCtx,
            ILangCompilationProvider compilationProvider, IStatementExecutorFactory statementExecutorFactory,
            IStorageComponentProvider componentProvider) {
        super(ctx, paths);
        this.appCtx = appCtx;
        this.compilationProvider = compilationProvider;
        this.parserFactory = compilationProvider.getParserFactory();
        this.statementExecutorFactory = statementExecutorFactory;
        this.componentProvider = componentProvider;
    }

    /**
     * Initialize the Content-Type of the response, and construct a
     * SessionConfig with the appropriate output writer and output-format
     * based on the Accept: header and other servlet parameters.
     */
    static SessionOutput initResponse(IServletRequest request, IServletResponse response) throws IOException {
        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, HttpUtil.Encoding.UTF8);
        // CLEAN_JSON output is the default; most generally useful for a
        // programmatic HTTP API
        OutputFormat format = OutputFormat.CLEAN_JSON;
        // First check the "output" servlet parameter.
        String output = request.getParameter("output");
        String accept = request.getHeader("Accept", "");
        if (output != null) {
            if ("CSV".equals(output)) {
                format = OutputFormat.CSV;
            } else if ("ADM".equals(output)) {
                format = OutputFormat.ADM;
            }
        } else {
            // Second check the Accept: HTTP header.
            if (accept.contains("application/x-adm")) {
                format = OutputFormat.ADM;
            } else if (accept.contains("text/csv")) {
                format = OutputFormat.CSV;
            }
        }
        PlanFormat planFormat = PlanFormat.get(request.getParameter("plan-format"), "plan format",
                PlanFormat.STRING, LOGGER);

        // If it's JSON, check for the "lossless" flag

        if (format == OutputFormat.CLEAN_JSON
                && ("true".equals(request.getParameter("lossless")) || accept.contains("lossless=true"))) {
            format = OutputFormat.LOSSLESS_JSON;
        }

        SessionOutput.ResultAppender appendHandle = (app, handle) -> app.append("{ \"").append("handle")
                .append("\":" + " \"").append(handle).append("\" }");
        SessionConfig sessionConfig = new SessionConfig(format, planFormat);

        // If it's JSON or ADM, check for the "wrapper-array" flag. Default is
        // "true" for JSON and "false" for ADM. (Not applicable for CSV.)
        boolean wrapperArray = format == OutputFormat.CLEAN_JSON || format == OutputFormat.LOSSLESS_JSON;
        String wrapperParam = request.getParameter("wrapper-array");
        if (wrapperParam != null) {
            wrapperArray = Boolean.valueOf(wrapperParam);
        } else if (accept.contains("wrap-array=true")) {
            wrapperArray = true;
        } else if (accept.contains("wrap-array=false")) {
            wrapperArray = false;
        }
        sessionConfig.set(SessionConfig.FORMAT_WRAPPER_ARRAY, wrapperArray);
        // Now that format is set, output the content-type
        switch (format) {
        case ADM:
            HttpUtil.setContentType(response, "application/x-adm");
            break;
        case CLEAN_JSON:
            // No need to reflect "clean-ness" in output type; fall through
        case LOSSLESS_JSON:
            HttpUtil.setContentType(response, "application/json");
            break;
        case CSV:
            // Check for header parameter or in Accept:.
            if ("present".equals(request.getParameter("header")) || accept.contains("header=present")) {
                HttpUtil.setContentType(response, "text/csv; header=present");
                sessionConfig.set(SessionConfig.FORMAT_CSV_HEADER, true);
            } else {
                HttpUtil.setContentType(response, "text/csv; header=absent");
            }
            break;
        default:
            throw new IOException("Unknown format " + format);
        }
        return new SessionOutput(sessionConfig, response.writer(), null, null, appendHandle, null);
    }

    @Override
    protected void get(IServletRequest request, IServletResponse response) {
        getOrPost(request, response);
    }

    @Override
    protected void post(IServletRequest request, IServletResponse response) {
        getOrPost(request, response);
    }

    private void getOrPost(IServletRequest request, IServletResponse response) {
        try {
            String query = query(request);
            // enable cross-origin resource sharing
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            SessionOutput sessionOutput = initResponse(request, response);
            QueryTranslator.ResultDelivery resultDelivery = whichResultDelivery(request);
            doHandle(response, query, sessionOutput, resultDelivery);
        } catch (Exception e) {
            response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
            LOGGER.log(Level.WARNING, "Failure handling request", e);
            return;
        }
    }

    private void doHandle(IServletResponse response, String query, SessionOutput sessionOutput,
            ResultDelivery resultDelivery) throws JsonProcessingException {
        try {
            response.setStatus(HttpResponseStatus.OK);
            IHyracksClientConnection hcc = (IHyracksClientConnection) ctx.get(HYRACKS_CONNECTION_ATTR);
            IHyracksDataset hds = (IHyracksDataset) ctx.get(HYRACKS_DATASET_ATTR);
            if (hds == null) {
                synchronized (ctx) {
                    hds = (IHyracksDataset) ctx.get(HYRACKS_DATASET_ATTR);
                    if (hds == null) {
                        hds = new HyracksDataset(hcc, appCtx.getCompilerProperties().getFrameSize(),
                                ResultReader.NUM_READERS);
                        ctx.put(HYRACKS_DATASET_ATTR, hds);
                    }
                }
            }
            IParser parser = parserFactory.createParser(query);
            List<Statement> aqlStatements = parser.parse();
            validate(aqlStatements);
            MetadataManager.INSTANCE.init();
            IStatementExecutor translator = statementExecutorFactory.create(appCtx, aqlStatements, sessionOutput,
                    compilationProvider, componentProvider);
            final IRequestParameters requestParameters = new RequestParameters(hds, resultDelivery,
                    new IStatementExecutor.Stats(), null, null, null);
            translator.compileAndExecute(hcc, null, requestParameters);
        } catch (AsterixException | TokenMgrError | org.apache.asterix.aqlplus.parser.TokenMgrError pe) {
            response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
            GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, pe.getMessage(), pe);
            String errorMessage = ResultUtil.buildParseExceptionMessage(pe, query);
            ObjectNode errorResp = ResultUtil.getErrorResponse(2, errorMessage, "",
                    ResultUtil.extractFullStackTrace(pe));
            sessionOutput.out().write(OBJECT_MAPPER.writeValueAsString(errorResp));
        } catch (Exception e) {
            GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, e.getMessage(), e);
            response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
            ResultUtil.apiErrorHandler(sessionOutput.out(), e);
        }
    }

    //TODO: Both Get and Post of this API must use the same parameter names
    private String query(IServletRequest request) {
        if (request.getHttpRequest().method() == HttpMethod.POST) {
            return HttpUtil.getRequestBody(request);
        } else {
            return getQueryParameter(request);
        }
    }

    private void validate(List<Statement> aqlStatements) throws AsterixException {
        for (Statement st : aqlStatements) {
            if ((st.getCategory() & getAllowedCategories()) == 0) {
                throw new AsterixException(String.format(getErrorMessage(), st.getKind()));
            }
        }
    }

    protected QueryTranslator.ResultDelivery whichResultDelivery(IServletRequest request) {
        String mode = request.getParameter("mode");
        if (mode != null) {
            if ("asynchronous".equals(mode) || "async".equals(mode)) {
                return QueryTranslator.ResultDelivery.ASYNC;
            } else if ("asynchronous-deferred".equals(mode) || "deferred".equals(mode)) {
                return QueryTranslator.ResultDelivery.DEFERRED;
            }
        }
        return QueryTranslator.ResultDelivery.IMMEDIATE;
    }

    protected abstract String getQueryParameter(IServletRequest request);

    protected abstract byte getAllowedCategories();

    protected abstract String getErrorMessage();
}