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

com.wavemaker.runtime.rest.RestInvocationHandler 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;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import com.wavemaker.commons.MessageResource;
import com.wavemaker.commons.WMRuntimeException;
import com.wavemaker.runtime.commons.WMObjectMapper;
import com.wavemaker.runtime.rest.model.HttpRequestData;
import com.wavemaker.runtime.rest.model.HttpResponseDetails;
import com.wavemaker.runtime.rest.service.RestRuntimeService;

import feign.Headers;
import feign.Param;
import feign.QueryMap;
import feign.RequestLine;

public class RestInvocationHandler implements InvocationHandler {

    private static Logger logger = LoggerFactory.getLogger(RestInvocationHandler.class);

    private static Pattern pathParameterPattern = Pattern.compile("\\/\\{(\\w*)\\}");
    private static Pattern queryParameterPattern = Pattern.compile("=\\{(\\w*)\\}");
    private static Pattern headerParameterPattern = Pattern.compile("\\{(\\w*)\\}");
    private static Pattern splitHeaderPattern = Pattern.compile("([^:]+):\\s*(.+)");

    private RestRuntimeService restRuntimeService;

    private String serviceId;

    public RestInvocationHandler(String serviceId, RestRuntimeService restRuntimeService) {
        this.serviceId = serviceId;
        this.restRuntimeService = restRuntimeService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        HttpRequestData httpRequestData = new HttpRequestData();
        Map pathVariablesMap = new HashMap<>();
        MultiValueMap queryVariablesMap = new LinkedMultiValueMap<>();
        Map headerVariableMap = new HashMap<>();
        MultiValueMap formVariableMap = new LinkedMultiValueMap<>();
        List queryVariablesList = getQueryVariables(method.getAnnotation(RequestLine.class).value());
        List headerPlaceholders = extractHeaderPlaceholders(method.getAnnotation(Headers.class).value());
        boolean urlEncodedHeaderPresent = urlEncodedHeaderPresent(method.getAnnotation(Headers.class).value());
        int position = 0;
        for (Annotation[] parameterAnnotation : method.getParameterAnnotations()) {
            if (args[position] != null) {
                if (parameterAnnotation.length != 0 && parameterAnnotation[0] instanceof Param) {
                    if (queryVariablesList.contains(((Param) parameterAnnotation[0]).value())) {
                        queryVariablesMap.add(((Param) parameterAnnotation[0]).value(), args[position].toString());
                    } else if (headerPlaceholders.contains(((Param) parameterAnnotation[0]).value())) {
                        headerVariableMap.put(((Param) parameterAnnotation[0]).value(), args[position].toString());
                    } else if (urlEncodedHeaderPresent) {
                        formVariableMap.add(((Param) parameterAnnotation[0]).value(), args[position]);
                    } else {
                        pathVariablesMap.put(((Param) parameterAnnotation[0]).value(), args[position].toString());
                    }
                } else if (parameterAnnotation.length != 0 && parameterAnnotation[0] instanceof QueryMap) {
                    queryVariablesMap.addAll((MultiValueMap) args[position]);
                } else {
                    //set as request body
                    httpRequestData.setRequestBody(new ByteArrayInputStream(WMObjectMapper.getInstance().writeValueAsBytes(args[position])));
                    httpRequestData.getHttpHeaders().add("content-type", "application/json");
                    //TODO:file
                }
            }
            position++;
        }

        httpRequestData.setQueryParametersMap(queryVariablesMap);
        httpRequestData.setPathVariablesMap(pathVariablesMap);

        StringBuilder requestBodyBuilder = new StringBuilder();
        if (!formVariableMap.isEmpty()) {
            formVariableMap.forEach((key, values) -> {
                values.forEach(value -> {
                    if (requestBodyBuilder.length() > 0) {
                        requestBodyBuilder.append("&");
                    }
                    if (value instanceof String) {
                        requestBodyBuilder.append(key).append("=").append((String) value);
                    }
                });
            });
            try {
                httpRequestData.setRequestBody(new ByteArrayInputStream(WMObjectMapper.getInstance().
                    writeValueAsBytes(requestBodyBuilder.toString())));
            } catch (IOException e) {
                logger.error(String.valueOf(e));
            }
        }
        logger.debug("constructed request data {}", httpRequestData.getPathVariablesMap());
        logger.debug("constructed request query param {}", httpRequestData.getQueryParametersMap());

        //Resolving the headers and setting them to httpRequestData
        //eg: if Header is like X-RapidAPI-Key: {x_RapidAPI_Key} in this case we will look for x_RapidAPI_Key in variableMap and set its value
        Arrays.stream(method.getAnnotation(Headers.class).value()).forEach(header -> {
            Matcher matcher = splitHeaderPattern.matcher(header);
            while (matcher.find()) {
                httpRequestData.getHttpHeaders().add(matcher.group(1), header.contains("{") ? headerVariableMap.get(matcher.group(2)) :
                    matcher.group(2));
            }
        });
        String[] split = method.getAnnotation(RequestLine.class).value().split(" ");
        HttpResponseDetails responseDetails = restRuntimeService.executeRestCall(serviceId,
            split[1].contains("?") ? split[1].subSequence(0, split[1].indexOf("?")).toString() : split[1],
            split[0],
            httpRequestData, RestExecutor.getRequestContextThreadLocal());

        try {
            if (method.getReturnType() != void.class) {
                if (responseDetails.getStatusCode() >= 200 && responseDetails.getStatusCode() < 300) {
                    if (method.getGenericReturnType() instanceof ParameterizedType) {
                        return WMObjectMapper.getInstance().readValue(responseDetails.getBody(),
                            WMObjectMapper.getInstance().getTypeFactory()
                                .constructCollectionType((Class) Class.forName(method.getReturnType().getName()),
                                    Class.forName(((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0].getTypeName(),
                                        true, Thread.currentThread().getContextClassLoader())));
                    }
                    return WMObjectMapper.getInstance().readValue(responseDetails.getBody(), method.getReturnType());
                } else {
                    logger.error(IOUtils.toString(responseDetails.getBody(), Charset.defaultCharset()));
                    throw new WMRuntimeException(MessageResource.create("com.wavemaker.runtime.$RestServiceInvocationError"), responseDetails.getStatusCode());
                }

            }
        } catch (IOException e) {
            throw new WMRuntimeException(e);
        }
        return null;
    }

    private List extractHeaderPlaceholders(String[] value) {
        List headerVariables = new ArrayList();
        Arrays.stream(value).forEach(header -> {
            Matcher matcher = headerParameterPattern.matcher(header);
            while (matcher.find()) {
                headerVariables.add(matcher.group(1));
            }
        });
        return headerVariables;
    }

    private Queue getPathVariables(String value) {
        Queue arrayList = new LinkedList<>();
        Matcher matcher = pathParameterPattern.matcher(value);
        while (matcher.find()) {
            arrayList.add(matcher.group(1));
        }
        return arrayList;
    }

    private List getQueryVariables(String value) {
        List queryVariables = new ArrayList();
        Matcher matcher = queryParameterPattern.matcher(value);
        while (matcher.find()) {
            queryVariables.add(matcher.group(1));
        }
        return queryVariables;

    }

    private boolean urlEncodedHeaderPresent(String[] headerList) {
        return Arrays.stream(headerList)
            .anyMatch(header -> header.contains("application/x-www-form-urlencoded"));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy