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

com.wavemaker.runtime.rest.service.RestRuntimeService Maven / Gradle / Ivy

There is a newer version: 11.9.2.ee
Show newest version
/*******************************************************************************
 * Copyright (C) 2022-2023 WaveMaker, 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.wavemaker.runtime.rest.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.commons.text.StringSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import com.wavemaker.commons.MessageResource;
import com.wavemaker.commons.UnAuthorizedResourceAccessException;
import com.wavemaker.commons.WMRuntimeException;
import com.wavemaker.commons.comparator.UrlComparator;
import com.wavemaker.commons.comparator.UrlStringComparator;
import com.wavemaker.commons.swaggerdoc.util.SwaggerDocUtil;
import com.wavemaker.commons.util.WMUtils;
import com.wavemaker.commons.web.filter.RequestTrackingFilter;
import com.wavemaker.commons.web.filter.ServerTimingMetric;
import com.wavemaker.runtime.commons.util.PropertyPlaceHolderReplacementHelper;
import com.wavemaker.runtime.rest.RequestContext;
import com.wavemaker.runtime.rest.RequestDataBuilder;
import com.wavemaker.runtime.rest.RestConstants;
import com.wavemaker.runtime.rest.model.HttpRequestData;
import com.wavemaker.runtime.rest.model.HttpRequestDetails;
import com.wavemaker.runtime.rest.model.HttpResponseDetails;
import com.wavemaker.runtime.rest.processor.RestRuntimeConfig;
import com.wavemaker.runtime.rest.processor.data.HttpRequestDataProcessor;
import com.wavemaker.runtime.rest.processor.data.HttpRequestDataProcessorContext;
import com.wavemaker.runtime.rest.processor.request.HttpRequestProcessor;
import com.wavemaker.runtime.rest.processor.request.HttpRequestProcessorContext;
import com.wavemaker.runtime.rest.processor.response.HttpResponseProcessor;
import com.wavemaker.runtime.rest.processor.response.HttpResponseProcessorContext;
import com.wavemaker.runtime.rest.util.HttpRequestUtils;
import com.wavemaker.runtime.rest.util.RestRequestUtils;
import com.wavemaker.tools.apidocs.tools.core.model.Operation;
import com.wavemaker.tools.apidocs.tools.core.model.ParameterType;
import com.wavemaker.tools.apidocs.tools.core.model.Path;
import com.wavemaker.tools.apidocs.tools.core.model.Swagger;
import com.wavemaker.tools.apidocs.tools.core.model.auth.ApiKeyAuthDefinition;
import com.wavemaker.tools.apidocs.tools.core.model.auth.BasicAuthDefinition;
import com.wavemaker.tools.apidocs.tools.core.model.auth.OAuth2Definition;
import com.wavemaker.tools.apidocs.tools.core.model.auth.SecuritySchemeDefinition;
import com.wavemaker.tools.apidocs.tools.core.model.parameters.Parameter;
import com.wavemaker.tools.apidocs.tools.core.model.parameters.RefParameter;
import com.wavemaker.tools.apidocs.tools.core.utils.PathUtils;

/**
 * @author Uday Shankar
 */
public class RestRuntimeService {

    private static final String AUTHORIZATION = "authorization";
    private static final Logger logger = LoggerFactory.getLogger(RestRuntimeService.class);
    private static final String OPERATION_DOES_NOT_EXIST = "com.wavemaker.runtime.operation.doesnt.exist";
    private RestRuntimeServiceCacheHelper restRuntimeServiceCacheHelper;

    @Autowired
    private RequestTrackingFilter requestTrackingFilter;

    @Autowired
    private PropertyPlaceHolderReplacementHelper propertyPlaceHolderReplacementHelper;

    @Autowired
    private Environment environment;

    private PathMatcher pathMatcher = new AntPathMatcher();

    @Autowired
    private RestConnector restConnector;

    @PostConstruct
    public void init() {
        restRuntimeServiceCacheHelper = new RestRuntimeServiceCacheHelper();
        restRuntimeServiceCacheHelper.setPropertyPlaceHolderReplacementHelper(propertyPlaceHolderReplacementHelper);
        restRuntimeServiceCacheHelper.setEnvironment(environment);
    }

    public HttpResponseDetails executeRestCall(String serviceId, String operationId, HttpRequestData httpRequestData) {
        return executeRestCall(serviceId, operationId, httpRequestData, null);
    }

    public HttpResponseDetails executeRestCall(String serviceId, String operationId, HttpRequestData httpRequestData,
                                               HttpServletRequest httpServletRequest) {
        Swagger swagger = restRuntimeServiceCacheHelper.getSwaggerDoc(serviceId);
        Triple pathAndOperation = findPathAndOperation(swagger, operationId);
        HttpRequestDetails httpRequestDetails = constructHttpRequest(serviceId, pathAndOperation.getLeft(),
            SwaggerDocUtil.getOperationType(pathAndOperation.getMiddle(), pathAndOperation.getRight().getOperationId()).toUpperCase(), httpRequestData);
        return executeRestCallWithProcessors(serviceId, httpRequestDetails, httpRequestData, httpServletRequest, "");
    }

    public void executeRestCall(String serviceId, String path, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        HttpRequestData httpRequestData = constructRequestData(httpServletRequest);
        HttpRequestDataProcessorContext httpRequestDataProcessorContext = new HttpRequestDataProcessorContext(httpServletRequest, httpRequestData);
        List httpRequestDataProcessors = restRuntimeServiceCacheHelper.getHttpRequestDataProcessors(serviceId);
        String context = httpServletRequest.getRequestURI();
        for (HttpRequestDataProcessor httpRequestDataProcessor : httpRequestDataProcessors) {
            logger.debug("Executing the httpRequestDataProcessor {} on the context {}", httpRequestDataProcessor, context);
            httpRequestDataProcessor.process(httpRequestDataProcessorContext);
        }
        executeRestCall(serviceId, path, httpRequestData, httpServletRequest, httpServletResponse, context);
    }

    public HttpResponseDetails executeRestCall(String serviceId, String path, String method, HttpRequestData httpRequestData, RequestContext requestContext) {
        HttpRequestDetails httpRequestDetails = constructHttpRequest(serviceId, path, method, httpRequestData);
        if (requestContext != null) {
            HttpHeaders httpHeaders = httpRequestDetails.getHeaders();
            if (!requestContext.getHeaders().isEmpty()) {
                httpHeaders.putAll(requestContext.getHeaders());
                httpRequestDetails.setHeaders(httpHeaders);
            }
            if (!requestContext.getQueryParams().isEmpty()) {
                StringBuilder endpointAddress = new StringBuilder(httpRequestDetails.getEndpointAddress());
                updateUrlWithQueryParameters(endpointAddress, new HashMap<>(httpRequestData.getQueryParametersMap()));
                httpRequestDetails.setEndpointAddress(endpointAddress.toString());
            }
        }
        return executeRestCallWithProcessors(serviceId, httpRequestDetails, httpRequestData, null, "");
    }

    public void executeRestCall(String serviceId, String path, final HttpRequestData httpRequestData,
                                final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final String context) {
        HttpRequestDetails httpRequestDetails = constructHttpRequest(serviceId, path, httpServletRequest.getMethod(), httpRequestData);
        HttpResponseDetails httpResponseDetails = executeRestCallWithProcessors(serviceId, httpRequestDetails, httpRequestData, httpServletRequest, context);
        try {
            HttpRequestUtils.writeResponse(httpResponseDetails, httpServletResponse);
        } catch (IOException e) {
            throw new WMRuntimeException(e);
        }
    }

    public HttpResponseDetails executeRestCallWithProcessors(String serviceId, HttpRequestDetails httpRequestDetails, final HttpRequestData httpRequestData,
                                                             final HttpServletRequest httpServletRequest, final String context) {
        HttpRequestProcessorContext httpRequestProcessorContext = new HttpRequestProcessorContext(httpServletRequest, httpRequestDetails, httpRequestData);
        final RestRuntimeConfig restRuntimeConfig = restRuntimeServiceCacheHelper.getAppRuntimeConfig(serviceId);
        List httpRequestProcessors = restRuntimeConfig.getHttpRequestProcessorList();
        for (HttpRequestProcessor httpRequestProcessor : httpRequestProcessors) {
            logger.debug("Executing the httpRequestProcessor {} on the context {}", httpRequestProcessor, context);
            httpRequestProcessor.process(httpRequestProcessorContext);
        }

        logger.debug("Rest service request details {}", httpRequestDetails);

        HttpResponseDetails httpResponseDetails = new HttpResponseDetails();
        long start = System.currentTimeMillis();
        restConnector.invokeRestCall(httpRequestDetails, response -> {
            long processingTime = System.currentTimeMillis() - start;
            logger.info("Time taken by the rest server endpoint is {}", processingTime);
            requestTrackingFilter.addServerTimingMetrics(new ServerTimingMetric("rest-server", processingTime, null));

            try {
                httpResponseDetails.setStatusCode(response.getRawStatusCode());
                httpResponseDetails.setBody(response.getBody());
            } catch (IOException e) {
                throw new WMRuntimeException(e);
            }
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.putAll(response.getHeaders());
            httpResponseDetails.setHeaders(httpHeaders);

            HttpResponseProcessorContext httpResponseProcessorContext = new HttpResponseProcessorContext(httpServletRequest, httpResponseDetails, httpRequestDetails, httpRequestData);
            List httpResponseProcessors = restRuntimeConfig.getHttpResponseProcessorList();
            for (HttpResponseProcessor httpResponseProcessor : httpResponseProcessors) {
                logger.debug("Executing the httpResponseProcessor {} on the context {}", httpResponseProcessor, context);
                httpResponseProcessor.process(httpResponseProcessorContext);
            }

            logger.debug("Rest service response details for the context {} is {}", context, httpResponseDetails);
        });
        return httpResponseDetails;
    }

    private HttpRequestData constructRequestData(HttpServletRequest httpServletRequest) {
        HttpRequestData httpRequestData;
        try {
            httpRequestData = new RequestDataBuilder().getRequestData(httpServletRequest);
        } catch (Exception e) {
            throw new WMRuntimeException(MessageResource.create("com.wavemaker.runtime.HttpRequestData.construction.failed"), e);
        }
        return httpRequestData;
    }

    private HttpRequestDetails constructHttpRequest(String serviceId, String path, String method, HttpRequestData httpRequestData) {
        Swagger swagger = restRuntimeServiceCacheHelper.getSwaggerDoc(serviceId);
        final Pair operationPair = findOperation(swagger, path, method);

        HttpHeaders httpHeaders = new HttpHeaders();
        Map queryParameters = new HashMap<>();
        Map pathParameters = new HashMap<>();
        Operation operation = operationPair.getRight();
        filterAndApplyServerVariablesOnRequestData(httpRequestData, swagger, operation,
            httpHeaders, queryParameters, pathParameters);

        HttpRequestDetails httpRequestDetails = new HttpRequestDetails();

        updateAuthorizationInfo(serviceId, swagger.getSecurityDefinitions(), operation, queryParameters, httpHeaders, httpRequestData);
        httpRequestDetails.setEndpointAddress(getEndPointAddress(serviceId, operationPair.getLeft(), queryParameters, pathParameters));
        httpRequestDetails.setMethod(method);

        httpRequestDetails.setHeaders(httpHeaders);
        httpRequestDetails.setBody(httpRequestData.getRequestBody());

        return httpRequestDetails;
    }

    private Triple findPathAndOperation(Swagger swagger, String operationId) {
        for (final Map.Entry pathEntry : swagger.getPaths().entrySet()) {
            for (final Operation operation : pathEntry.getValue().getOperations()) {
                if (operation.getMethodName().equalsIgnoreCase(operationId)) {
                    return ImmutableTriple.of(pathEntry.getKey(), pathEntry.getValue(), operation);
                }
            }
        }
        throw new WMRuntimeException(MessageResource.create(OPERATION_DOES_NOT_EXIST),
            operationId);
    }

    private Pair findOperation(Swagger swagger, String path, String method) {
        Map swaggerPaths = swagger.getPaths();

        List pathEntries = new ArrayList<>(swaggerPaths.keySet());
        pathEntries.sort(new UrlComparator<>() {
            @Override
            public String getUrlPattern(String s) {
                return s;
            }
        });
        pathEntries.sort(new UrlStringComparator<>() {
            @Override
            public String getUrlPattern(String s) {
                return s;
            }
        });

        for (String pathString : pathEntries) {
            if (pathMatcher.match(pathString, path)) {
                Operation operation = PathUtils.getOperation(swaggerPaths.get(pathString), method);
                if (operation != null) {
                    return ImmutablePair.of(pathString, operation);
                }
            }
        }
        throw new WMRuntimeException(MessageResource.create(OPERATION_DOES_NOT_EXIST),
            path);
    }

    private void filterAndApplyServerVariablesOnRequestData(
        HttpRequestData httpRequestData, Swagger swagger, Operation operation, HttpHeaders headers,
        Map queryParameters, Map pathParameters) {
        for (Parameter parameter : operation.getParameters()) {
            if (parameter instanceof RefParameter) {
                parameter = swagger.getParameter(((RefParameter) parameter).getSimpleRef());
            }
            String paramName = parameter.getName();
            String type = parameter.getIn().toUpperCase();
            Optional variableValue = RestRequestUtils.findVariableValue(parameter);
            if (ParameterType.HEADER.name().equals(type)) {
                List headerValues = variableValue.map(Collections::singletonList)
                    .orElse(httpRequestData.getHttpHeaders().get(paramName));
                if (headerValues != null) {
                    headers.put(paramName, headerValues);
                }
            } else if (ParameterType.QUERY.name().equals(type)) {
                List paramValues = variableValue.map(Collections::singletonList)
                    .orElse(httpRequestData.getQueryParametersMap().get(paramName));
                if (paramValues != null) {
                    queryParameters.put(paramName, paramValues);
                }
            } else if (ParameterType.PATH.name().equals(type)) {
                String pathVariableValue = variableValue
                    .orElse(httpRequestData.getPathVariablesMap().get(paramName));
                if (pathVariableValue != null) {
                    pathParameters.put(paramName, pathVariableValue);
                }
            }
        }
    }

    private String getEndPointAddress(String serviceId, String pathValue, Map queryParameters, Map pathParameters) {
        String scheme = getPropertyValue(serviceId, RestConstants.SCHEME_KEY);
        String host = getPropertyValue(serviceId, RestConstants.HOST_KEY);
        String basePath = getPropertyValue(serviceId, RestConstants.BASE_PATH_KEY);

        StringBuilder sb = new StringBuilder(scheme).append("://").append(host)
            .append(getNormalizedString(basePath)).append(getNormalizedString(pathValue));

        updateUrlWithQueryParameters(sb, queryParameters);

        StringSubstitutor stringSubstitutor = new StringSubstitutor(pathParameters, "{", "}");
        return stringSubstitutor.replace(sb.toString());
    }

    private void updateUrlWithQueryParameters(StringBuilder endpointAddressSb, Map queryParameters) {
        boolean first = true;
        for (Map.Entry queryParam : queryParameters.entrySet()) {
            String[] strings = WMUtils.getStringList(queryParam.getValue());
            for (String str : strings) {
                if (first) {
                    endpointAddressSb.append("?");
                } else {
                    endpointAddressSb.append("&");
                }
                endpointAddressSb.append(queryParam.getKey()).append("=").append(str);
                first = false;
            }
        }
    }

    private void updateAuthorizationInfo(String serviceId, Map securitySchemeDefinitionMap, Operation operation, Map queryParameters, HttpHeaders httpHeaders, HttpRequestData httpRequestData) {
        //check basic auth is there for operation
        List>> securityMap = operation.getSecurity();
        if (securityMap != null) {
            for (Map> securityList : securityMap) {
                for (Map.Entry> security : securityList.entrySet()) {
                    //TODO update the code to handle if multiple securityConfigurations are enabled for the api.
                    SecuritySchemeDefinition securitySchemeDefinition = securitySchemeDefinitionMap.get(security.getKey());
                    if (securitySchemeDefinition instanceof OAuth2Definition) {
                        OAuth2Definition oAuth2Definition = (OAuth2Definition) securitySchemeDefinition;
                        if (ParameterType.QUERY.name().equalsIgnoreCase(oAuth2Definition.getSendAccessTokenAs())) {
                            queryParameters.put(oAuth2Definition.getAccessTokenParamName(), httpRequestData.getQueryParametersMap().getFirst(oAuth2Definition
                                .getAccessTokenParamName()));
                        } else {
                            sendAsAuthorizationHeader(httpHeaders, httpRequestData);
                        }
                    } else if (securitySchemeDefinition instanceof BasicAuthDefinition) {
                        sendAsAuthorizationHeader(httpHeaders, httpRequestData);
                    } else if (securitySchemeDefinition instanceof ApiKeyAuthDefinition) {
                        ApiKeyAuthDefinition apiKeyAuthDefinition = (ApiKeyAuthDefinition) securitySchemeDefinition;
                        String apiKeyName = apiKeyAuthDefinition.getName();
                        String sanitizedApiKeyName = Arrays.stream(apiKeyName.split("\\W+")).collect(Collectors.joining());
                        if (ParameterType.QUERY.name().equalsIgnoreCase(apiKeyAuthDefinition.getIn().toString())) {
                            String value = getPropertyValue(serviceId, "apikey.query." + sanitizedApiKeyName);
                            if (value != null) {
                                queryParameters.put(apiKeyAuthDefinition.getName(), value);
                            }
                        }
                        if (ParameterType.HEADER.name().equalsIgnoreCase(apiKeyAuthDefinition.getIn().toString())) {
                            String value = getPropertyValue(serviceId, "apikey.header." + sanitizedApiKeyName);
                            if (value != null) {
                                httpHeaders.set(apiKeyAuthDefinition.getName(), value);
                            }
                        }
                    }
                }
            }
        }
    }

    private void sendAsAuthorizationHeader(HttpHeaders httpHeaders, HttpRequestData httpRequestData) {
        String authorizationHeaderValue = httpRequestData.getHttpHeaders().getFirst(AUTHORIZATION);
        if (authorizationHeaderValue == null) {
            throw new UnAuthorizedResourceAccessException("Authorization details are not specified in the request headers");
        }
        httpHeaders.set(RestConstants.AUTHORIZATION, authorizationHeaderValue);
    }

    private String getNormalizedString(String str) {
        return (str != null) ? str.trim() : "";
    }

    private String getPropertyValue(String serviceId, String key) {
        String fullKey = "rest." + serviceId + "." + key;
        return environment.getProperty(fullKey);
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy