All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dell.doradus.service.rest.RESTRequest Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Dell, 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 com.dell.doradus.service.rest;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.servlet.http.HttpServletRequest;

import com.dell.doradus.common.ApplicationDefinition;
import com.dell.doradus.common.ContentType;
import com.dell.doradus.common.HttpDefs;
import com.dell.doradus.common.HttpMethod;
import com.dell.doradus.common.TableDefinition;
import com.dell.doradus.common.Utils;
import com.dell.doradus.service.db.Tenant;

/**
 * Wrapper for an HTTP request (as an {@link HttpServletRequest} and the variables, if
 * any, extracted from the request URI. Provides convenience methods to access request
 * headers and parameters, including getting the input message body as a String, byte[],
 * or InputStream.
 */
public class RESTRequest {
    // Request members that this object wraps:
    private final HttpServletRequest    m_request;
    private final Map   m_variableMap;
    private final Tenant                m_tenant;
    private final ApplicationDefinition m_appDef;

    // Extracted members for easy access:
    private ContentType   m_contentTypeIn;      // from Content-Type
    private ContentType   m_contentTypeOut;     // from Accept-Type
    private byte[]        m_requestEntity;      // raw input message (may be compressed)
    private boolean       m_bEntityCompressed;  // true if content entity is compressed

    /**
     * Create an object that wraps the given request parameters.
     *
     * @param tenant        {@link Tenant} that defines this request's context (may be null).
     * @param appDef        {@link ApplicationDefinition} corresponding to this request's
     *                      {application}, if relevant, otherwise null.
     * @param request       Request as received by Servlet interface.
     * @param variableMap   Variables extracted from the REST URI (should be decoded).
     * @throws IOException 
     */
    public RESTRequest(Tenant tenant, ApplicationDefinition appDef, HttpServletRequest request, Map variableMap) {
        m_tenant = tenant;
        m_appDef = appDef;
        m_request = request;
        m_variableMap = variableMap;
        setRequestMembers();
    }   // constructor

    /**
     * Get the {@link ApplicationDefinition} associated with this request's
     * "{application}". If this is not an application command, a RuntimeException is
     * thrown.
     * 
     * @return {@link ApplicationDefinition} associated with this request. 
     *         Will not be null.
     * @throws NotFoundException if the application requested by this request is not
     *         defined.
     */
    public ApplicationDefinition getAppDef() throws NotFoundException {
        if (m_appDef == null) {
            throw new RuntimeException("getAppDef() called for non-application command");
        }
        return m_appDef;
    }   // getAppDef
    
    /**
     * Convenience method that gets the {@link TableDefinition} of the table defined by
     * the decoded "{table}" variable in the current request from the given application
     * definition. If the given table is not found, an IllegalArgumentException is thrown
     * so the REST API can turn it into a 400 Bad Request response.
     *
     * @param  appDef   {@link ApplicationDefinition} of application to get table for.
     * @return          {@link TableDefinition} of table. Won't be null since an exception
     *                  is thrown if the table isn't found.
     */
    public TableDefinition getTableDef(ApplicationDefinition appDef) {
        assert appDef != null;
        String table = getVariableDecoded("table");
        if (Utils.isEmpty(table)) {
            throw new RuntimeException("Missing {table} variable");
        }
        TableDefinition tableDef = m_appDef.getTableDef(table);
        if (tableDef == null) {
            throw new IllegalArgumentException("Unknown table for application '" + m_appDef.getAppName() + "': " + table);
        }
        return tableDef;
    }   // getTableDef
    
    /**
     * Get the {@link Tenant} that defines the context for this request.
     * 
     * @return  This request's Tenant context, which defines the application(s) to which
     *          the request will be applied.
     */
    public Tenant getTenant() {
        return m_tenant;
    }   // getTenant
    
    /**
     * Get the variables extracted for this REST request. For example, if the request has
     * the URI:
     * 
     *      /{application}/{table}/_query?{query}
     * 
* and the actual request used in this request is: *
     *      /foo/bar/_query?q=cat+dog
     * 
* The variables returned by this method consists of the following key/value pairs: *
     *      application=foo
     *      table=bar
     *      query=q=cat+dog
     * 
* Note that variable values are not URI-decoded since they may need processing before * being decoded. * * @return A map of variables extracted for this REST request, if any, URI-encoded. * The map will be empty if there are no variables defined by this request's * URI. */ public Map getVariables() { return m_variableMap; } // getVariables /** * Get the undecoded value of the variable with the given name. Null is returned if * there is no variable with the given name. * * @param variableName Variable name. * @return The value of requested variable URI-decoded, or null if there * is no such variable. * @see #getVariables() * @see #getVariableDecoded(String) */ public String getVariable(String variableName) { return m_variableMap.get(variableName); } // getVariable /** * Get the URI-decoded value of the variable with the given name. Null is returned if * there is no variable with the given name. This method calls * {@link #getVariable(String)} and passes the result through * {@link Utils#urlDecode(String)}. * * @param variableName Variable name. * @return The value of requested variable URI-decoded, or null if there * is no such variable. * @see #getVariables() * @see #getVariable(String) */ public String getVariableDecoded(String variableName) { String value = m_variableMap.get(variableName); if (value == null) { return null; } return Utils.urlDecode(value); } // getVariable /** * Get the value of the content-length header for this REST request. It could be 0 or * even -1 if the request has no input entity. * * @return The value of the content-length header for this REST request. */ public int getContentLength() { return m_request.getContentLength(); } // getContentLength /** * Get the content-type header of this REST request as a {@link ContentType}. If no * content-type was specified, the default content-type is returned. * * @return The content-type of this REST request as a {@link ContentType}. */ public ContentType getInputContentType() { return m_contentTypeIn; } // getInputContentType /** * Get the accept header of this REST request as a {@link ContentType}. If no accept * header was specified, the same value as {@link #getInputContentType()} is returned. * * @return The accept header of this REST request as a {@link ContentType}. */ public ContentType getOutputContentType() { return m_contentTypeOut; } // getOutputContentType /** * Get the input entity (body) of this REST request as a string. If no input entity * was provided, an empty string is returned. If an input entity exists and is * compressed, it is decompressed first. The binary input entity is converted to a * string using UTF-8. * * @return The input entity (body) of this REST request as a string. It is empty if * there is no input string. */ public String getInputBody() { if (m_requestEntity.length == 0 || !m_bEntityCompressed) { return Utils.toString(m_requestEntity); } else { try { return Utils.toString(Utils.decompressGZIP(m_requestEntity)); } catch (IOException e) { throw new IllegalArgumentException("Error decompressing input: " + e.toString()); } } } // getInputBody /** * Get the input entity (body) of this REST request as an InputStream. If no input * entity was provided, null is returned. If an input entity is present and is * compressed, the stream returned is the decompressed byte stream. * @return The input entity (body) of this REST request as an InputStream, returning * decompressed bytes if necessary. Null is returned if there is no input * stream. */ public InputStream getInputStream() { if (m_requestEntity.length == 0) { return null; } ByteArrayInputStream bis = new ByteArrayInputStream(m_requestEntity); InputStream inStream = bis; if (m_bEntityCompressed) { try { inStream = new GZIPInputStream(bis); } catch (IOException e) { throw new IllegalArgumentException("Error decompressing input: " + e.toString()); } } return inStream; } // getInputStream /** * Return the {@link HttpMethod} corresponding to the method used for this request. * * @return Value such as {@link HttpMethod#GET}. */ public HttpMethod getMethod() { return HttpMethod.methodFromString(m_request.getMethod()); } // getMethod /** * Get the value of the command header with the given name. Null is returned if no * value is assigned to the given header. * * @param headerName REST command header (case-insensitve). Example: authorization. * @return Header value or null if no such header exists. */ public String getRequestHeader(String headerName) { return m_request.getHeader(headerName); } // getRequestHeader ///// Private methods // Extract convenience members, which could detect problems with input parameters. private void setRequestMembers() { m_contentTypeIn = getContentType(); m_contentTypeOut = getAcceptType(); m_requestEntity = readRequestBody(); m_bEntityCompressed = isMessageCompressed(); } // setRequestMembers // Get the request's content-type, using XML as the default. private ContentType getContentType() { String contentTypeValue = m_request.getContentType(); if (contentTypeValue == null) { return ContentType.TEXT_XML; } return new ContentType(contentTypeValue); } // getContentType // Get the request's accept type, defaulting to content-type if none is specified. private ContentType getAcceptType() { // If the format header is present, it overrides the ACCEPT header. String format = m_variableMap.get("format"); if (format != null) { return new ContentType(format); } String acceptParts = m_request.getHeader(HttpDefs.ACCEPT); if (!Utils.isEmpty(acceptParts)) { for (String acceptPart : acceptParts.split(",")) { ContentType acceptType = new ContentType(acceptPart); if (acceptType.isJSON() || acceptType.isXML() || acceptType.isPlainText()) { return acceptType; } } } return getContentType(); } // getAcceptType // If Content-Encoding is included, verify that we support it and return true. private boolean isMessageCompressed() { String contentEncoding = m_request.getHeader(HttpDefs.CONTENT_ENCODING); if (contentEncoding != null) { if (!contentEncoding.equalsIgnoreCase("gzip")) { throw new IllegalArgumentException("Unsupported Content-Encoding: " + contentEncoding); } return true; } return false; } // isMessageCompressed // Extract the input entity (body) of this request. If there is no input entity, a // zero-length byte[] is returned. The message is *not* decompressed. private byte[] readRequestBody() { int bytesLeft = m_request.getContentLength(); if (bytesLeft <= 0) { return new byte[0]; } int totalBytesRead = 0; try { byte[] byteBuffer = new byte[bytesLeft]; try (InputStream inputStream = m_request.getInputStream()) { int bytesRead = -1; int offset = 0; while ((bytesRead = inputStream.read(byteBuffer, offset, bytesLeft)) > 0) { offset += bytesRead; bytesLeft -= bytesRead; totalBytesRead += bytesRead; } } return byteBuffer; } catch (IOException e) { String errMsg = String.format("Error reading from input request; expected %d bytes; only read %d bytes", m_request.getContentLength(), totalBytesRead); throw new RuntimeException(errMsg, e); } } // readRequestBody } // class RESTRequest




© 2015 - 2025 Weber Informatics LLC | Privacy Policy