com.wavemaker.runtime.rest.RestInvocationHandler Maven / Gradle / Ivy
/*******************************************************************************
* 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 extends Collection>) 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"));
}
}