Skip to content

Commit a26d2c5

Browse files
committed
Finished Web MVC's GraphQlHttpHandler
1 parent 2f32879 commit a26d2c5

File tree

2 files changed

+108
-84
lines changed

2 files changed

+108
-84
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.springframework.graphql.server.support;
2+
3+
import org.springframework.web.multipart.MultipartFile;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.regex.Pattern;
8+
9+
// As in DGS, this is borrowed from https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/eb4dfdb5c0198adc1b4d4466c3b4ea4a77def5d1/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/core/internal/VariableMapper.java
10+
public class MultipartVariableMapper {
11+
12+
private static final Pattern PERIOD = Pattern.compile("\\.");
13+
14+
private static final MultipartVariableMapper.Mapper<Map<String, Object>> MAP_MAPPER =
15+
new MultipartVariableMapper.Mapper<Map<String, Object>>() {
16+
@Override
17+
public Object set(Map<String, Object> location, String target, MultipartFile value) {
18+
return location.put(target, value);
19+
}
20+
21+
@Override
22+
public Object recurse(Map<String, Object> location, String target) {
23+
return location.get(target);
24+
}
25+
};
26+
private static final MultipartVariableMapper.Mapper<List<Object>> LIST_MAPPER =
27+
new MultipartVariableMapper.Mapper<List<Object>>() {
28+
@Override
29+
public Object set(List<Object> location, String target, MultipartFile value) {
30+
return location.set(Integer.parseInt(target), value);
31+
}
32+
33+
@Override
34+
public Object recurse(List<Object> location, String target) {
35+
return location.get(Integer.parseInt(target));
36+
}
37+
};
38+
39+
@SuppressWarnings({"unchecked", "rawtypes"})
40+
public static void mapVariable(String objectPath, Map<String, Object> variables, MultipartFile part) {
41+
String[] segments = PERIOD.split(objectPath);
42+
43+
if (segments.length < 2) {
44+
throw new RuntimeException("object-path in map must have at least two segments");
45+
} else if (!"variables".equals(segments[0])) {
46+
throw new RuntimeException("can only map into variables");
47+
}
48+
49+
Object currentLocation = variables;
50+
for (int i = 1; i < segments.length; i++) {
51+
String segmentName = segments[i];
52+
MultipartVariableMapper.Mapper mapper = determineMapper(currentLocation, objectPath, segmentName);
53+
54+
if (i == segments.length - 1) {
55+
if (null != mapper.set(currentLocation, segmentName, part)) {
56+
throw new RuntimeException("expected null value when mapping " + objectPath);
57+
}
58+
} else {
59+
currentLocation = mapper.recurse(currentLocation, segmentName);
60+
if (null == currentLocation) {
61+
throw new RuntimeException(
62+
"found null intermediate value when trying to map " + objectPath);
63+
}
64+
}
65+
}
66+
}
67+
68+
private static MultipartVariableMapper.Mapper<?> determineMapper(
69+
Object currentLocation, String objectPath, String segmentName) {
70+
if (currentLocation instanceof Map) {
71+
return MAP_MAPPER;
72+
} else if (currentLocation instanceof List) {
73+
return LIST_MAPPER;
74+
}
75+
76+
throw new RuntimeException(
77+
"expected a map or list at " + segmentName + " when trying to map " + objectPath);
78+
}
79+
80+
interface Mapper<T> {
81+
82+
Object set(T location, String target, MultipartFile value);
83+
84+
Object recurse(T location, String target);
85+
}
86+
}

spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlHttpHandler.java

Lines changed: 22 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.Optional;
25-
import java.util.regex.Pattern;
2625

2726
import javax.servlet.ServletException;
2827

2928
import com.fasterxml.jackson.core.JsonProcessingException;
3029
import com.fasterxml.jackson.core.type.TypeReference;
30+
import com.fasterxml.jackson.databind.ObjectMapper;
3131
import org.apache.commons.logging.Log;
3232
import org.apache.commons.logging.LogFactory;
33+
import org.springframework.graphql.server.support.MultipartVariableMapper;
3334
import org.springframework.web.multipart.MultipartFile;
3435
import org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest;
3536
import reactor.core.publisher.Mono;
@@ -69,15 +70,32 @@ public class GraphQlHttpHandler {
6970

7071
private final WebGraphQlHandler graphQlHandler;
7172

73+
private final ObjectMapper objectMapper;
74+
7275
/**
7376
* Create a new instance.
7477
* @param graphQlHandler common handler for GraphQL over HTTP requests
78+
* @deprecated Use GraphQlHttpHandler(WebGraphQlHandler graphQlHandler, ObjectMapper objectMapper) instead.
7579
*/
76-
public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler) {
80+
@Deprecated
81+
public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler) {
7782
Assert.notNull(graphQlHandler, "WebGraphQlHandler is required");
7883
this.graphQlHandler = graphQlHandler;
84+
this.objectMapper = new ObjectMapper();
7985
}
8086

87+
/**
88+
* Create a new instance.
89+
* @param graphQlHandler common handler for GraphQL over HTTP requests
90+
* @param objectMapper ObjectMapper used for parsing form parts
91+
*/
92+
public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler, ObjectMapper objectMapper) {
93+
Assert.notNull(graphQlHandler, "WebGraphQlHandler is required");
94+
Assert.notNull(objectMapper, "ObjectMapper is required");
95+
this.graphQlHandler = graphQlHandler;
96+
this.objectMapper = objectMapper;
97+
}
98+
8199
/**
82100
* Handle GraphQL requests over HTTP.
83101
* @param serverRequest the incoming HTTP request
@@ -112,7 +130,7 @@ public ServerResponse handleRequest(ServerRequest serverRequest) throws ServletE
112130
public ServerResponse handleMultipartRequest(ServerRequest serverRequest) throws ServletException {
113131
Optional<String> operation = serverRequest.param("operations");
114132
Optional<String> mapParam = serverRequest.param("map");
115-
Map<String, Object> inputQuery = readJson(operation, new TypeReference<>() {});
133+
Map<String, Object> inputQuery = readJson(operation, new TypeReference<Map<String, Object>>() {});
116134
final Map<String, Object> queryVariables;
117135
if (inputQuery.containsKey("variables")) {
118136
queryVariables = (Map<String, Object>)inputQuery.get("variables");
@@ -125,7 +143,7 @@ public ServerResponse handleMultipartRequest(ServerRequest serverRequest) throws
125143
}
126144

127145
Map<String, MultipartFile> fileParams = getMultipartMap(serverRequest);
128-
Map<String, List<String>> fileMapInput = readJson(mapParam, new TypeReference<>() {});
146+
Map<String, List<String>> fileMapInput = readJson(mapParam, new TypeReference<Map<String, List<String>>>() {});
129147
fileMapInput.forEach((String fileKey, List<String> objectPaths) -> {
130148
MultipartFile file = fileParams.get(fileKey);
131149
if (file != null) {
@@ -210,83 +228,3 @@ private static MediaType selectResponseMediaType(ServerRequest serverRequest) {
210228
}
211229

212230
}
213-
214-
// As in DGS, this is borrowed from https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/eb4dfdb5c0198adc1b4d4466c3b4ea4a77def5d1/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/core/internal/VariableMapper.java
215-
class MultipartVariableMapper {
216-
217-
private static final Pattern PERIOD = Pattern.compile("\\.");
218-
219-
private static final Mapper<Map<String, Object>> MAP_MAPPER =
220-
new Mapper<Map<String, Object>>() {
221-
@Override
222-
public Object set(Map<String, Object> location, String target, MultipartFile value) {
223-
return location.put(target, value);
224-
}
225-
226-
@Override
227-
public Object recurse(Map<String, Object> location, String target) {
228-
return location.get(target);
229-
}
230-
};
231-
private static final Mapper<List<Object>> LIST_MAPPER =
232-
new Mapper<List<Object>>() {
233-
@Override
234-
public Object set(List<Object> location, String target, MultipartFile value) {
235-
return location.set(Integer.parseInt(target), value);
236-
}
237-
238-
@Override
239-
public Object recurse(List<Object> location, String target) {
240-
return location.get(Integer.parseInt(target));
241-
}
242-
};
243-
244-
@SuppressWarnings({"unchecked", "rawtypes"})
245-
public static void mapVariable(String objectPath, Map<String, Object> variables, MultipartFile part) {
246-
String[] segments = PERIOD.split(objectPath);
247-
248-
if (segments.length < 2) {
249-
throw new RuntimeException("object-path in map must have at least two segments");
250-
} else if (!"variables".equals(segments[0])) {
251-
throw new RuntimeException("can only map into variables");
252-
}
253-
254-
Object currentLocation = variables;
255-
for (int i = 1; i < segments.length; i++) {
256-
String segmentName = segments[i];
257-
Mapper mapper = determineMapper(currentLocation, objectPath, segmentName);
258-
259-
if (i == segments.length - 1) {
260-
if (null != mapper.set(currentLocation, segmentName, part)) {
261-
throw new RuntimeException("expected null value when mapping " + objectPath);
262-
}
263-
} else {
264-
currentLocation = mapper.recurse(currentLocation, segmentName);
265-
if (null == currentLocation) {
266-
throw new RuntimeException(
267-
"found null intermediate value when trying to map " + objectPath);
268-
}
269-
}
270-
}
271-
}
272-
273-
private static Mapper<?> determineMapper(
274-
Object currentLocation, String objectPath, String segmentName) {
275-
if (currentLocation instanceof Map) {
276-
return MAP_MAPPER;
277-
} else if (currentLocation instanceof List) {
278-
return LIST_MAPPER;
279-
}
280-
281-
throw new RuntimeException(
282-
"expected a map or list at " + segmentName + " when trying to map " + objectPath);
283-
}
284-
285-
interface Mapper<T> {
286-
287-
Object set(T location, String target, MultipartFile value);
288-
289-
Object recurse(T location, String target);
290-
}
291-
}
292-

0 commit comments

Comments
 (0)