Skip to content

Commit d523b5d

Browse files
committed
1.fix(rest-api): fix file upload issue for REST API #137
2.fix(graphql): Add default Content-Type: application/json 3.fix(query): application query execute should use application query timeout
1 parent 11e18a0 commit d523b5d

File tree

13 files changed

+183
-55
lines changed

13 files changed

+183
-55
lines changed

server/openblocks-plugins/graphqlPlugin/src/main/java/com/openblocks/plugin/graphql/GraphQLExecutor.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import com.openblocks.sdk.plugin.restapi.auth.RestApiAuthType;
7474
import com.openblocks.sdk.query.QueryVisitorContext;
7575
import com.openblocks.sdk.util.JsonUtils;
76+
import com.openblocks.sdk.util.MoreMapUtils;
7677
import com.openblocks.sdk.util.MustacheHelper;
7778
import com.openblocks.sdk.webclient.WebClients;
7879

@@ -143,6 +144,9 @@ public GraphQLQueryExecutionContext buildQueryExecutionContext(GraphQLDatasource
143144
String updatedQueryBody = renderMustacheString(queryBody, requestParams);
144145
String normalizedUrl = buildUrl(urlDomain, updatedQueryPath, requestParams);
145146
Map<String, String> allHeaders = buildHeaders(datasourceHeaders, updatedQueryHeaders);
147+
if (!MoreMapUtils.containsStringKeyIgnoreCase(allHeaders, HttpHeaders.CONTENT_TYPE)) {
148+
allHeaders.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
149+
}
146150
String contentType = parseContentType(allHeaders).toLowerCase();
147151
if (!isValidContentType(contentType)) {
148152
throw new PluginException(QUERY_ARGUMENT_ERROR, "INVALID_CONTENT_TYPE", contentType);
@@ -185,10 +189,9 @@ private String buildUrl(String urlDomain, String updatedQueryPath, Map<String, O
185189
}
186190

187191
private Map<String, String> buildHeaders(List<Property> datasourceHeaders, List<Property> updatedQueryHeaders) {
188-
return Stream.concat(datasourceHeaders.stream(),
189-
updatedQueryHeaders.stream())
192+
return Stream.concat(datasourceHeaders.stream(), updatedQueryHeaders.stream())
190193
.filter(it -> StringUtils.isNotBlank(it.getKey()) && StringUtils.isNotBlank(it.getValue()))
191-
.collect(Collectors.toUnmodifiableMap(property -> property.getKey().trim().toLowerCase(),
194+
.collect(Collectors.toMap(property -> property.getKey().trim(),
192195
Property::getValue,
193196
(oldValue, newValue) -> newValue));
194197
}
@@ -350,13 +353,6 @@ private Mono<ClientResponse> httpCall(WebClient webClient, HttpMethod httpMethod
350353
.flatMap(response -> {
351354
if (response.statusCode().is3xxRedirection()) {
352355
String redirectUrl = response.headers().header("Location").get(0);
353-
/*
354-
TODO
355-
In case the redirected URL is not absolute (complete), create the new URL using the relative path
356-
This particular scenario is seen in the URL : https://rickandmortyapi.com/api/character
357-
It redirects to partial URI : /api/character/
358-
In this scenario we should convert the partial URI to complete URI
359-
*/
360356
URI redirectUri;
361357
try {
362358
redirectUri = new URI(redirectUrl);
@@ -460,8 +456,7 @@ private ResponseBodyData parseResponseDataInfo(byte[] body, MediaType contentTyp
460456
.build();
461457
}
462458

463-
private BodyInserter<?, ? super ClientHttpRequest> buildBodyInserter(
464-
boolean isEncodeParams,
459+
private BodyInserter<?, ? super ClientHttpRequest> buildBodyInserter(boolean isEncodeParams,
465460
String requestContentType,
466461
String queryBody,
467462
List<Property> bodyFormData) {
@@ -472,8 +467,7 @@ private ResponseBodyData parseResponseDataInfo(byte[] body, MediaType contentTyp
472467
case MediaType.APPLICATION_JSON_VALUE:
473468
final Object bodyObject = parseJsonBody(queryBody);
474469
return BodyInserters.fromValue(bodyObject);
475-
case MediaType.APPLICATION_FORM_URLENCODED_VALUE:
476-
case MediaType.MULTIPART_FORM_DATA_VALUE:
470+
case MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE:
477471
return dataUtils.buildBodyInserter(bodyFormData, requestContentType, isEncodeParams);
478472
default:
479473
return BodyInserters.fromValue((queryBody).getBytes(StandardCharsets.ISO_8859_1));

server/openblocks-plugins/restApiPlugin/src/main/java/com/openblocks/plugin/restapi/RestApiExecutor.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static com.openblocks.sdk.exception.PluginCommonError.JSON_PARSE_ERROR;
3131
import static com.openblocks.sdk.exception.PluginCommonError.QUERY_ARGUMENT_ERROR;
3232
import static com.openblocks.sdk.exception.PluginCommonError.QUERY_EXECUTION_ERROR;
33+
import static com.openblocks.sdk.plugin.restapi.DataUtils.convertToMultiformFileValue;
3334
import static com.openblocks.sdk.plugin.restapi.DataUtils.parseJsonBody;
3435
import static com.openblocks.sdk.plugin.restapi.auth.RestApiAuthType.DIGEST_AUTH;
3536
import static com.openblocks.sdk.plugin.restapi.auth.RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN;
@@ -89,8 +90,10 @@
8990
import com.openblocks.sdk.exception.PluginException;
9091
import com.openblocks.sdk.models.Property;
9192
import com.openblocks.sdk.models.QueryExecutionResult;
93+
import com.openblocks.sdk.models.RestBodyFormFileData;
9294
import com.openblocks.sdk.plugin.common.QueryExecutor;
9395
import com.openblocks.sdk.plugin.restapi.DataUtils;
96+
import com.openblocks.sdk.plugin.restapi.MultipartFormData;
9497
import com.openblocks.sdk.plugin.restapi.RestApiDatasourceConfig;
9598
import com.openblocks.sdk.plugin.restapi.auth.AuthConfig;
9699
import com.openblocks.sdk.plugin.restapi.auth.BasicAuthConfig;
@@ -146,14 +149,15 @@ public RestApiQueryExecutionContext buildQueryExecutionContext(RestApiDatasource
146149

147150
List<Property> updatedQueryParams = renderMustacheValueInProperties(queryParams, requestParams);
148151
List<Property> updatedQueryHeaders = renderMustacheValueInProperties(queryHeaders, requestParams);
149-
List<Property> updatedQueryBodyParams = renderMustacheValueInProperties(queryBodyParams, requestParams);
150152

151153
Map<String, String> allHeaders = buildHeaders(datasourceHeaders, updatedQueryHeaders);
152154
String contentType = parseContentType(allHeaders).toLowerCase();
153155
if (!isValidContentType(contentType)) {
154156
throw new PluginException(QUERY_ARGUMENT_ERROR, "INVALID_CONTENT_TYPE", contentType);
155157
}
156158

159+
List<Property> updatedQueryBodyParams = renderMustacheValueForQueryBody(queryBodyParams, requestParams, contentType);
160+
157161
String updatedQueryBody;
158162
if (isJsonContentType(contentType)) {
159163
updatedQueryBody = renderMustacheJsonString(queryBody, requestParams);
@@ -183,6 +187,23 @@ public RestApiQueryExecutionContext buildQueryExecutionContext(RestApiDatasource
183187
.build();
184188
}
185189

190+
private List<Property> renderMustacheValueForQueryBody(List<Property> queryBodyParams, Map<String, Object> paramMap,
191+
String contentType) {
192+
return queryBodyParams.stream()
193+
.map(it -> {
194+
String renderedKey = renderMustacheString(it.getKey(), paramMap);
195+
if (MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType) && it.isMultipartFileType()) {
196+
List<MultipartFormData> multiformFileData = convertToMultiformFileValue(it.getValue(), paramMap);
197+
return new RestBodyFormFileData(renderedKey, multiformFileData);
198+
}
199+
200+
String renderedStringValue = renderMustacheString(it.getValue(), paramMap);
201+
return new Property(renderedKey, renderedStringValue, it.getType());
202+
})
203+
.toList();
204+
}
205+
206+
186207
private String mergeBody(String queryBody, List<Property> datasourceBody, String contentType) {
187208
if (CollectionUtils.isEmpty(datasourceBody)) {
188209
return queryBody;
@@ -438,7 +459,7 @@ private Map<String, String> buildHeaders(List<Property> datasourceHeaders, List<
438459
return Stream.concat(datasourceHeaders.stream(),
439460
updatedQueryHeaders.stream())
440461
.filter(it -> StringUtils.isNotBlank(it.getKey()) && StringUtils.isNotBlank(it.getValue()))
441-
.collect(Collectors.toUnmodifiableMap(property -> property.getKey().trim().toLowerCase(),
462+
.collect(Collectors.toUnmodifiableMap(property -> property.getKey().trim(),
442463
Property::getValue,
443464
(oldValue, newValue) -> newValue));
444465
}
@@ -472,7 +493,7 @@ private boolean isNoneContentType(String requestContentType) {
472493
return StringUtils.isBlank(requestContentType);
473494
}
474495

475-
private static List<Property> renderMustacheValueInProperties(List<Property> properties, Map<String, Object> paramMap) {
496+
private List<Property> renderMustacheValueInProperties(List<Property> properties, Map<String, Object> paramMap) {
476497
return properties.stream()
477498
.map(it -> {
478499
Property newProperty = new Property(renderMustacheString(it.getKey(), paramMap),

server/openblocks-sdk/src/main/java/com/openblocks/sdk/models/Property.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.openblocks.sdk.models;
22

33
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.openblocks.sdk.plugin.restapi.DataUtils.MultipartFormDataType;
45

56
import lombok.EqualsAndHashCode;
67
import lombok.ToString;
@@ -36,6 +37,10 @@ public String getType() {
3637
return type;
3738
}
3839

40+
public boolean isMultipartFileType() {
41+
return MultipartFormDataType.FILE.name().equalsIgnoreCase(type);
42+
}
43+
3944
public void setType(String type) {
4045
this.type = type;
4146
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.openblocks.sdk.models;
2+
3+
import java.util.List;
4+
5+
import com.openblocks.sdk.plugin.restapi.DataUtils.MultipartFormDataType;
6+
import com.openblocks.sdk.plugin.restapi.MultipartFormData;
7+
8+
public class RestBodyFormFileData extends Property {
9+
10+
private List<MultipartFormData> fileData;
11+
12+
public RestBodyFormFileData(String key, String value) {
13+
super(key, value);
14+
}
15+
16+
public RestBodyFormFileData(String key, String value, String type) {
17+
super(key, value, type);
18+
}
19+
20+
public RestBodyFormFileData(String key, List<MultipartFormData> fileData) {
21+
super(key, null, MultipartFormDataType.FILE.name());
22+
this.fileData = fileData;
23+
}
24+
25+
public List<MultipartFormData> getFileData() {
26+
return fileData;
27+
}
28+
}

server/openblocks-sdk/src/main/java/com/openblocks/sdk/plugin/restapi/DataUtils.java

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626
import java.nio.charset.StandardCharsets;
2727
import java.util.Base64;
2828
import java.util.List;
29+
import java.util.Map;
2930
import java.util.stream.Collectors;
3031

32+
import javax.annotation.Nonnull;
33+
3134
import org.apache.commons.collections4.CollectionUtils;
3235
import org.apache.commons.lang3.StringUtils;
3336
import org.springframework.http.MediaType;
@@ -36,13 +39,16 @@
3639
import org.springframework.web.reactive.function.BodyInserter;
3740
import org.springframework.web.reactive.function.BodyInserters;
3841

39-
import com.fasterxml.jackson.core.JsonProcessingException;
4042
import com.fasterxml.jackson.databind.DeserializationFeature;
43+
import com.fasterxml.jackson.databind.JsonNode;
4144
import com.fasterxml.jackson.databind.ObjectMapper;
45+
import com.google.common.collect.Streams;
4246
import com.google.gson.JsonSyntaxException;
4347
import com.openblocks.sdk.exception.PluginException;
4448
import com.openblocks.sdk.models.Property;
45-
import com.openblocks.sdk.util.JsonUtils;
49+
import com.openblocks.sdk.models.RestBodyFormFileData;
50+
import com.openblocks.sdk.util.ExceptionUtils;
51+
import com.openblocks.sdk.util.MustacheHelper;
4652

4753
import net.minidev.json.parser.JSONParser;
4854
import net.minidev.json.parser.ParseException;
@@ -144,20 +150,15 @@ public String parseFormData(List<Property> bodyFormData, boolean encodeParamsTog
144150
.filter(it -> StringUtils.isNotBlank(it.getKey()))
145151
.forEach(property -> {
146152
String key = property.getKey();
147-
if (property.getType() == null) {
148-
bodyBuilder.part(key, property.getValue());
149-
return;
150-
}
151-
MultipartFormDataType multipartFormDataType = MultipartFormDataType.valueOf(property.getType().toUpperCase());
152-
if (MultipartFormDataType.TEXT.equals(multipartFormDataType)) {
153+
if (!property.isMultipartFileType()) {
153154
bodyBuilder.part(key, property.getValue());
154155
return;
155156
}
156-
// File type
157+
157158
try {
158-
populateFileTypeBodyBuilder(bodyBuilder, property);
159-
} catch (JsonProcessingException e) {
160-
throw new PluginException(DATASOURCE_ARGUMENT_ERROR, "CONTENT_PARSE_ERROR");
159+
populateMultiformFileData(bodyBuilder, property);
160+
} catch (Exception e) {
161+
throw ExceptionUtils.wrapException(DATASOURCE_ARGUMENT_ERROR, "CONTENT_PARSE_ERROR", e);
161162
}
162163
});
163164

@@ -166,32 +167,51 @@ public String parseFormData(List<Property> bodyFormData, boolean encodeParamsTog
166167
});
167168
}
168169

169-
private void populateFileTypeBodyBuilder(MultipartBodyBuilder bodyBuilder, Property property) throws JsonProcessingException {
170-
String key = property.getKey();
171-
String fileValue = property.getValue();
172-
parseMultipartFormDataList(fileValue).forEach(multipartFormData -> {
173-
String name = multipartFormData.getName();
174-
String data = multipartFormData.getData();
175-
byte[] decodedValue = Base64.getDecoder().decode(data);
176-
bodyBuilder.part(key, decodedValue)
177-
.filename(name);
178-
});
170+
private void populateMultiformFileData(MultipartBodyBuilder bodyBuilder, Property property) {
171+
parseMultipartFormDataList(property)
172+
.forEach(multipartFormData -> {
173+
String name = multipartFormData.getName();
174+
String data = multipartFormData.getData();
175+
byte[] decodedValue = Base64.getDecoder().decode(data);
176+
bodyBuilder.part(property.getKey(), decodedValue)
177+
.filename(name);
178+
});
179179
}
180180

181-
private List<MultipartFormData> parseMultipartFormDataList(String fileValue) {
181+
private List<MultipartFormData> parseMultipartFormDataList(Property property) {
182+
if (property instanceof RestBodyFormFileData restBodyFormFileData) {
183+
return restBodyFormFileData.getFileData();
184+
}
185+
throw new PluginException(DATASOURCE_ARGUMENT_ERROR, "CONTENT_PARSE_ERROR");
186+
}
187+
188+
public static List<MultipartFormData> convertToMultiformFileValue(String fileValue, Map<String, Object> paramMap) {
182189
try {
183-
if (fileValue.startsWith("{")) {
184-
return List.of(objectMapper.readValue(fileValue, MultipartFormData.class));
190+
JsonNode jsonValue = MustacheHelper.renderMustacheJson(fileValue, paramMap);
191+
if (jsonValue.isObject()) {
192+
return List.of(parseMultipartFormData(jsonValue));
185193
}
186-
if (fileValue.startsWith("[")) {
187-
return JsonUtils.fromJsonList(fileValue, MultipartFormData.class);
194+
195+
if (jsonValue.isArray()) {
196+
return Streams.stream(jsonValue)
197+
.filter(JsonNode::isObject)
198+
.map(DataUtils::parseMultipartFormData)
199+
.toList();
188200
}
189-
} catch (Exception e) {
201+
} catch (Throwable ignored) {
190202
// ignore
191203
}
192204
throw new PluginException(DATASOURCE_ARGUMENT_ERROR, "CONTENT_PARSE_ERROR");
193205
}
194206

207+
@Nonnull
208+
private static MultipartFormData parseMultipartFormData(JsonNode jsonObject) {
209+
MultipartFormData result = new MultipartFormData();
210+
result.setData(jsonObject.get("data").asText());
211+
result.setName(jsonObject.get("name").asText());
212+
return result;
213+
}
214+
195215
private static Object parseJsonObject(String jsonString) throws ParseException {
196216
String trimmed = jsonString.trim();
197217

server/openblocks-sdk/src/main/java/com/openblocks/sdk/util/ExceptionUtils.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.openblocks.sdk.util;
22

3+
import static reactor.core.Exceptions.throwIfFatal;
4+
35
import com.openblocks.sdk.exception.BaseException;
46
import com.openblocks.sdk.exception.BizError;
57
import com.openblocks.sdk.exception.BizException;
@@ -34,30 +36,46 @@ public static PluginException ofPluginException(PluginError error, String messag
3436
}
3537

3638
public static BaseException wrapException(PluginError error, String messageKey, Throwable e) {
37-
if (e instanceof BaseException baseException) {
39+
BaseException baseException = castAsBaseException(e);
40+
if (baseException != null) {
3841
return baseException;
3942
}
4043
return ofPluginException(error, messageKey, e.getMessage());
4144
}
4245

4346
public static BaseException wrapException(BizError error, String messageKey, Throwable e) {
44-
if (e instanceof BaseException baseException) {
47+
BaseException baseException = castAsBaseException(e);
48+
if (baseException != null) {
4549
return baseException;
4650
}
4751
return new BizException(error, messageKey, e.getMessage());
4852
}
4953

5054
public static <T> Mono<T> propagateError(PluginError error, String messageKey, Throwable e) {
51-
if (e instanceof BaseException) {
52-
return Mono.error(e);
55+
BaseException baseException = castAsBaseException(e);
56+
if (baseException != null) {
57+
return Mono.error(baseException);
5358
}
5459
return ofPluginError(error, messageKey, e.getMessage());
5560
}
5661

5762
public static <T> Mono<T> propagateError(BizError error, String messageKey, Throwable e) {
58-
if (e instanceof BaseException) {
59-
return Mono.error(e);
63+
BaseException baseException = castAsBaseException(e);
64+
if (baseException != null) {
65+
return Mono.error(baseException);
6066
}
6167
return ofError(error, messageKey, e.getMessage());
6268
}
69+
70+
private static BaseException castAsBaseException(Throwable e) {
71+
throwIfFatal(e);
72+
if (e instanceof BaseException baseException) {
73+
return baseException;
74+
}
75+
if (e.getCause() instanceof BaseException baseException) {
76+
return baseException;
77+
}
78+
return null;
79+
}
80+
6381
}

0 commit comments

Comments
 (0)