Skip to content
This repository was archived by the owner on Oct 25, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions graphql-java-spring-webmvc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ dependencies {
compile "org.springframework:spring-context:$springVersion"
compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
compile "com.graphql-java:graphql-java:$graphqlJavaVersion"
compile "javax.servlet:javax.servlet-api:4.0.1"
compile "commons-fileupload:commons-fileupload:1.4"



testCompile("org.assertj:assertj-core:$assertJVersion")
testCompile group: 'junit', name: 'junit', version: '4.12'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
import org.springframework.web.server.ResponseStatusException;

import javax.naming.OperationNotSupportedException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

Expand All @@ -46,7 +54,7 @@ public Object graphqlPOST(
@RequestParam(value = "operationName", required = false) String operationName,
@RequestParam(value = "variables", required = false) String variablesJson,
@RequestBody(required = false) String body,
WebRequest webRequest) throws IOException {
WebRequest webRequest) throws IOException, OperationNotSupportedException {

if (body == null) {
body = "";
Expand All @@ -69,6 +77,45 @@ public Object graphqlPOST(
request.setQuery("");
}
return executeRequest(request.getQuery(), request.getOperationName(), request.getVariables(), webRequest);
}else if(contentType!=null && contentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)){
// In this case body is not mapped, same as all on @RequestParams.
// Because we send files as variables we just have to take care of variables part of deserialized GraphQLRequestBody.
// "map" parameter tells us how to link multiPartFiles to their variables.
// Map can look like this:
// {
// "0" : ["variables.files"],
// "1" : ["variables.filesList.0"],
// "2" : ["variables.filesList.1"]
// }
// It tells us that multipartFile
// that has a key "0" should be linked to variable named file, and multipartFile with a key "1" should be put as 0th indexed element
// in list named filesList in variables and "2" as a 1st element in the same list.
GraphQLRequestBody request = jsonSerializer.deserialize(webRequest.getParameter("operations"),GraphQLRequestBody.class);
if(request.getQuery() != null) {
LinkedHashMap<String, ArrayList<String>> multipartFileKeyVariablePathMap = this.jsonSerializer.deserialize(webRequest.getParameter("map"), LinkedHashMap.class);

MultipartHttpServletRequest multiPartRequest = (MultipartHttpServletRequest) ((ServletWebRequest) webRequest).getNativeRequest();
Map<String, MultipartFile> multipartFileMap = multiPartRequest.getFileMap();

for(Map.Entry<String,MultipartFile> e: multipartFileMap.entrySet()){
String pathString = multipartFileKeyVariablePathMap.get(e.getKey()).get(0); /*i.e. "variables.files" or "variables.fileList.NUMBER*/
if (pathString.matches("variables\\.[a-zA-Z0-9]*?\\.\\d")) {
String[] splittedPath = pathString.split("\\.", 3);
final Object variablesArray = request.getVariables().get(splittedPath[1]);
if (variablesArray instanceof ArrayList)
((ArrayList) variablesArray).set(Integer.parseInt(splittedPath[2]), e.getValue());
else
throw new OperationNotSupportedException("Array of files represented by not supported collection");
} else if (pathString.startsWith("variables.")) {
String[] splittedPath = pathString.split("\\.",2);
request.getVariables().put(splittedPath[1],e.getValue());
}
}
}else {
request.setQuery("");
}

return executeRequest(request.getQuery(),request.getOperationName(),request.getVariables(),webRequest);
}

// In addition to the above, we recommend supporting two additional cases:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;
import testconfig.TestAppConfig;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
Expand All @@ -41,6 +49,8 @@
@WebAppConfiguration
public class GraphQLControllerTest {



private MockMvc mockMvc;

@Autowired
Expand Down Expand Up @@ -307,4 +317,57 @@ public void testSimpleGetRequest() throws Exception {

}

@Test
public void testMultiPartRequest() throws Exception{
final String query ="mutation($fooInput : FooInput!, $file: Upload!, $files: [Upload]!){ doSomething(fooInput: $fooInput, f: $file, fList: $files) }";
final String variables="{ \"fooInput\":{ \"x\":\"foo\",\"y\":\"bar\"}, \"file\":null, \"files\":[null,null,null]}";
final String operationName = "null";

final String operationsParam = String.format("{\"query\":\"%s\", \"variables\":%s, \"operationName\":%s }",query,variables,operationName);
final String mapParam = "{\"0\":[\"variables.file\"],\"1\":[\"variables.files.0\"],\"2\":[\"variables.files.1\"],\"3\":[\"variables.files.2\"]}";

MockMultipartFile firstFile = new MockMultipartFile("0","f0.txt","text/plain","file 0 test test".getBytes());
MockMultipartFile secondFile = new MockMultipartFile("1","f1.txt","text/plain", "file 1 test".getBytes());
MockMultipartFile thirdFile = new MockMultipartFile("2","f2.txt","text/plain", "file 2 text".getBytes());
MockMultipartFile fourthFile = new MockMultipartFile("3","f3.txt","text/plain","file 3 test".getBytes());

ExecutionResultImpl executionResult = ExecutionResultImpl.newExecutionResult()
.data("bar")
.build();
CompletableFuture cf = CompletableFuture.completedFuture(executionResult);
ArgumentCaptor<ExecutionInput> captor = ArgumentCaptor.forClass(ExecutionInput.class);
Mockito.when(graphql.executeAsync(captor.capture())).thenReturn(cf);


MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart("/graphql")
.file(firstFile)
.file(secondFile)
.file(thirdFile)
.file(fourthFile)
.param("operations",operationsParam)
.param("map",mapParam);

MvcResult mvcResult = this.mockMvc.perform(requestBuilder)
.andExpect(status().isOk())
.andExpect(request().asyncStarted())
.andReturn();

this.mockMvc.perform(asyncDispatch(mvcResult))
.andDo(print()).andExpect(status().isOk())
.andExpect(jsonPath("data", is("bar")))
.andReturn();

assertThat(captor.getAllValues().size(), is(1));

assertThat(captor.getValue().getQuery(), is(query));
assertTrue(captor.getValue().getVariables().containsKey("fooInput"));

assertTrue(captor.getValue().getVariables().containsKey("file"));
assertThat(captor.getValue().getVariables().get("file"),instanceOf(MultipartFile.class));
assertThat(((MultipartFile) captor.getValue().getVariables().get("file")).getOriginalFilename(),is("f0.txt"));

assertTrue(captor.getValue().getVariables().containsKey("files"));
assertThat(((Collection)captor.getValue().getVariables().get("files")).size(),is(3));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
Expand All @@ -26,4 +28,10 @@ public ObjectMapper objectMapper() {
return new ObjectMapper();
}

@Bean
public MultipartResolver multiPartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver;
}

}