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

com.dell.doradus.service.rest.RESTServlet 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.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.cassandra.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dell.doradus.common.HttpCode;
import com.dell.doradus.common.HttpDefs;
import com.dell.doradus.common.RESTResponse;
import com.dell.doradus.common.Utils;
import com.dell.doradus.core.ServerConfig;
import com.dell.doradus.service.db.DBNotAvailableException;
import com.dell.doradus.service.db.DuplicateException;
import com.dell.doradus.service.db.Tenant;
import com.dell.doradus.service.db.UnauthorizedException;
import com.dell.doradus.service.tenant.TenantService;

/**
 * An HttpServlet implementation used by the {@link RESTService} to process REST requests.
 * Each request is mapped to a {@link RESTCommand} and processed via its callback. If the
 * the request is unknown or the callback creates 
 *
 */
public class RESTServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private final Logger m_logger = LoggerFactory.getLogger(getClass().getSimpleName());
    
    //----- Inherited methods from HttpServlet
    
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            long startNano = System.nanoTime();
            RESTService.instance().onNewrequest();

            // Extract the query component of the request, if any, but move api, format,
            // and tenant parameters to the variable map if present. This removes them
            // from the URI pattern for matching.
            Map variableMap = new HashMap();
            String query = extractQueryParam(request, variableMap);
            RESTCommand cmd = RESTService.instance().matchCommand(request.getMethod(),
                                                                  request.getRequestURI(),
                                                                  query,
                                                                  variableMap);
            if (cmd == null) {
                throw new NotFoundException("Request does not match a known URI: " + request.getRequestURL());
            }
            Utils.require(cmd != null, "Request does not match a known URI: " + request.getRequestURL());
            
            Tenant tenant = getTenant(cmd, request, variableMap);
            RESTRequest restRequest = new RESTRequest(tenant, request, variableMap);
            RESTCallback callback = cmd.getNewCallback(restRequest);
            RESTResponse restResponse = callback.invoke();
            
            if(restResponse.getCode().getCode() >= 300) {
                RESTService.instance().onRequestRejected(restResponse.getCode().toString());
            } else {
                RESTService.instance().onRequestSuccess(startNano);
            }
            sendResponse(response, restResponse);
            m_logger.debug("Elapsed time: {} millis; request={}",
                           (float)(System.nanoTime() - startNano)/1000000, getFullURI(request));
        } catch (IllegalArgumentException e) {
            // 400 Bad Request
            RESTResponse restResponse = new RESTResponse(HttpCode.BAD_REQUEST, e.getMessage());
            m_logger.info("Returning client error: {}; request: {}",
                          restResponse.toString(), getFullURI(request));
            RESTService.instance().onRequestRejected(restResponse.getCode().toString());
            sendResponse(response, restResponse);
        } catch (NotFoundException e) {
            // 404 Not Found
            RESTResponse restResponse = new RESTResponse(HttpCode.NOT_FOUND, e.getMessage());
            m_logger.info("Returning client error: {}; request: {}",
                          restResponse.toString(), getFullURI(request));
            RESTService.instance().onRequestRejected(restResponse.getCode().toString());
            sendResponse(response, restResponse);
        } catch (DBNotAvailableException e) {
            // 503 Service Unavailable
            RESTResponse restResponse = new RESTResponse(HttpCode.SERVICE_UNAVAILABLE, e.getMessage());
            m_logger.info("Returning service error: {}; request: {}",
                          restResponse.toString(), getFullURI(request));
            RESTService.instance().onRequestRejected(restResponse.getCode().toString());
            sendResponse(response, restResponse);
        } catch (UnauthorizedException e) {
            // 401 Unauthorized
            RESTResponse restResponse = new RESTResponse(HttpCode.UNAUTHORIZED, e.getMessage());
            m_logger.info("Returning client error: {}; request: {}",
                          restResponse.toString(), getFullURI(request));
            RESTService.instance().onRequestRejected(restResponse.getCode().toString());
            sendResponse(response, restResponse);
        } catch (DuplicateException e) {
            // 409 Conflict
            RESTResponse restResponse = new RESTResponse(HttpCode.CONFLICT, e.getMessage());
            m_logger.info("Returning client error: {}; request: {}",
                          restResponse.toString(), getFullURI(request));
            RESTService.instance().onRequestRejected(restResponse.getCode().toString());
            sendResponse(response, restResponse);
        } catch (Throwable e) {
            // 500 Internal Error: include a stack trace and report in log.
            m_logger.error("Unexpected exception handling request: " + getFullURI(request), e);
            String stackTrace = Utils.getStackTrace(e);
            RESTResponse restResponse = new RESTResponse(HttpCode.INTERNAL_ERROR, stackTrace);
            RESTService.instance().onRequestFailed(e);
            sendResponse(response, restResponse);
        }
    }    // goGet
    
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }    // doPost
    
    /**
     * @see HttpServlet#doPut(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }    // doPut
    
    /**
     * @see HttpServlet#doDelete(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }    // doDelete
    
    //----- Private methods

    // Decide the Tenant context for this command and multi-tenant configuration options.
    private Tenant getTenant(RESTCommand cmd, HttpServletRequest request, Map variableMap) {
        Tenant tenant = null;
        String tenantName = variableMap.get("tenant");
        String authorizationHeader = request.getHeader("Authorization");
        if (ServerConfig.getInstance().multitenant_mode) {
            if (cmd.isSystemCommand()) {
                tenant = TenantService.instance().validateSystemUser(authorizationHeader);
            } else {
                if (Utils.isEmpty(tenantName)) {
                    Utils.require(!ServerConfig.getInstance().disable_default_keyspace,
                                  "'tenant' parameter is required for this command");
                    tenant = TenantService.instance().getDefaultTenant();
                } else {
                    tenant = TenantService.instance().validateTenant(tenantName, authorizationHeader);
                }
            }
        } else {
            tenant = TenantService.instance().getDefaultTenant();
        }
        return tenant;
    }   // getTenant
    
    // Send the given response, which includes a response code and optionally a body
    // and/or additional response headers. If the body is non-empty, we automatically add
    // the Content-Length and a Content-Type of Text/plain.
    private void sendResponse(HttpServletResponse servletResponse, RESTResponse restResponse) throws IOException {
        servletResponse.setStatus(restResponse.getCode().getCode());
        
        Map responseHeaders = restResponse.getHeaders();
        if (responseHeaders != null) {
            for (Map.Entry mapEntry : responseHeaders.entrySet()) {
                if (mapEntry.getKey().equalsIgnoreCase(HttpDefs.CONTENT_TYPE)) {
                    servletResponse.setContentType(mapEntry.getValue());
                } else {
                    servletResponse.setHeader(mapEntry.getKey(), mapEntry.getValue());
                }
            }
        }
        
        byte[] bodyBytes = restResponse.getBodyBytes();
        int bodyLen = bodyBytes == null ? 0 : bodyBytes.length;
        servletResponse.setContentLength(bodyLen);
        
        if (bodyLen > 0 && servletResponse.getContentType() == null) {
            servletResponse.setContentType("text/plain");
        }
        
        if (bodyLen > 0) {
            servletResponse.getOutputStream().write(restResponse.getBodyBytes());
        }
    }   // sendResponse
    
    // Extract and return the query component of the given request, but move "api=x",
    // "format=y", and "tenant=z", if present, to rest parameters.
    private String extractQueryParam(HttpServletRequest request, Map restParams) {
        String query = request.getQueryString();
        if (Utils.isEmpty(query)) {
            return "";
        }
        StringBuilder buffer = new StringBuilder(query);
        
        // Split query component into decoded, &-separate components.
        String[] parts = Utils.splitURIQuery(buffer.toString());
        List> unusedList = new ArrayList>();
        boolean bRewrite = false;
        for (String part : parts) {
            Pair param = extractParam(part);
            switch (param.left.toLowerCase()) {
            case "api":
                bRewrite = true;
                restParams.put("api", param.right);
                break;
            case "format":
                bRewrite = true;
                if (param.right.equalsIgnoreCase("xml")) {
                    restParams.put("format", "text/xml");
                } else if (param.right.equalsIgnoreCase("json")) {
                    restParams.put("format", "application/json");
                }
                break;
            case "tenant":
                bRewrite = true;
                restParams.put("tenant", param.right);
                break;
            default:
                unusedList.add(param);
            }
        }
        
        // If we extracted any fixed params, rewrite the query parameter.
        if (bRewrite) {
            buffer.setLength(0);
            for (Pair pair : unusedList) {
                if (buffer.length() > 0) {
                    buffer.append("&");
                }
                buffer.append(Utils.urlEncode(pair.left));
                if (pair.right != null) {
                    buffer.append("=");
                    buffer.append(Utils.urlEncode(pair.right));
                }
            }
        }
        return buffer.toString();
    }   // extractQueryParam

    // Split the given k=v (or just k) param into a Pair object.
    private Pair extractParam(String part) {
        int eqInx = part.indexOf('=');
        String paramName;
        String paramValue;
        if (eqInx < 0) {
            paramName = part;
            paramValue = null;
        } else {
            paramName = part.substring(0, eqInx);
            paramValue = part.substring(eqInx + 1);
        }
        return Pair.create(paramName, paramValue);
    }   // extractParam
    
    // Reconstruct the entire URI from the given request.
    private String getFullURI(HttpServletRequest request) {
        StringBuilder buffer = new StringBuilder(request.getMethod());
        buffer.append(" ");
        buffer.append(request.getRequestURI());
        String queryParam = request.getQueryString();
        if (!Utils.isEmpty(queryParam)) {
            buffer.append("?");
            buffer.append(queryParam);
        }
        return buffer.toString();
    }   // getFullURI
    
}   // class RESTServlet




© 2015 - 2025 Weber Informatics LLC | Privacy Policy