You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2019/01/16 10:00:14 UTC
[servicecomb-java-chassis] 01/04: [SCB-1046] file upload support
file array for the same name
This is an automated email from the ASF dual-hosted git repository.
liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git
commit 122583e94aa939c20aa325d70f517aeeb94ff789
Author: heyile <25...@qq.com>
AuthorDate: Sat Dec 8 17:15:27 2018 +0800
[SCB-1046] file upload support file array for the same name
---
.../common/rest/codec/RestClientRequest.java | 4 +-
.../rest/codec/param/FormProcessorCreator.java | 30 +++++-
.../rest/codec/param/RestClientRequestImpl.java | 61 +++++++++---
.../VertxServerRequestToHttpServletRequest.java | 10 ++
.../apache/servicecomb/it/testcase/TestUpload.java | 82 ++++++++++++++++
.../servicecomb/it/schema/UploadJaxrsSchema.java | 108 +++++++++++++++++++++
.../it/schema/UploadSpringmvcSchema.java | 96 ++++++++++++++++++
.../reference/CommonToHttpServletRequest.java | 65 ++++++++++++-
.../springmvc/reference/CseClientHttpRequest.java | 3 +-
.../core/CommonParameterTypeProcessor.java | 5 +-
.../swagger/generator/core/OperationGenerator.java | 23 +++++
.../parametertype/HttpServletRequestProcessor.java | 4 +-
...r.java => MultipartFileArrayTypeProcessor.java} | 13 ++-
...or.java => MultipartFileListTypeProcessor.java} | 17 +++-
.../parameter/MultipartFileTypeProcessor.java | 4 +-
...ger.generator.core.CommonParameterTypeProcessor | 2 +
.../swagger/invocation/converter/ConverterMgr.java | 35 ++++++-
.../generator/InvocationContextProcessor.java | 4 +-
.../converter/SpringMultipartArrayConverter.java | 56 +++++++++++
.../converter/SpringMultipartListConverter.java | 57 +++++++++++
20 files changed, 640 insertions(+), 39 deletions(-)
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java
index a300273..9b36049 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java
@@ -17,8 +17,6 @@
package org.apache.servicecomb.common.rest.codec;
-import javax.servlet.http.Part;
-
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
@@ -41,5 +39,5 @@ public interface RestClientRequest {
Buffer getBodyBuffer() throws Exception;
- void attach(String name, Part part);
+ void attach(String name, Object part);
}
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java
index 04ea5bf..65b9fb5 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java
@@ -18,7 +18,10 @@
package org.apache.servicecomb.common.rest.codec.param;
import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
@@ -26,6 +29,7 @@ import javax.ws.rs.core.Response.Status;
import org.apache.servicecomb.common.rest.RestConst;
import org.apache.servicecomb.common.rest.codec.RestClientRequest;
+import org.apache.servicecomb.foundation.vertx.http.FileUploadPart;
import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
import com.fasterxml.jackson.databind.JavaType;
@@ -34,6 +38,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
import io.swagger.models.parameters.FormParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.properties.FileProperty;
+import io.swagger.models.properties.Property;
public class FormProcessorCreator implements ParamValueProcessorCreator {
public static final String PARAMTYPE = "formData";
@@ -99,22 +104,41 @@ public class FormProcessorCreator implements ParamValueProcessorCreator {
}
private boolean isPart(Parameter parameter) {
- return new FileProperty().getType().equals(((FormParameter) parameter).getType());
+ //only check
+ FormParameter formParameter = (FormParameter) parameter;
+ if ("array".equals(formParameter.getType())) {
+ Property items = formParameter.getItems();
+ if (items != null) {
+ return new FileProperty().getType().equals(items.getType());
+ }
+ }
+ return new FileProperty().getType().equals(formParameter.getType());
}
- private static class PartProcessor extends AbstractParamProcessor {
+ public static class PartProcessor extends AbstractParamProcessor {
PartProcessor(String paramPath, JavaType targetType, Object defaultValue, boolean required) {
super(paramPath, targetType, defaultValue, required);
}
@Override
public Object getValue(HttpServletRequest request) throws Exception {
+
+ if (targetType.getRawClass().equals(List.class)) {
+ JavaType contentType = targetType.getContentType();
+ if (contentType != null && contentType.getRawClass().equals(Part.class)) {
+ //get all parts
+ Collection<Part> parts = request.getParts();
+ List<Part> collect = parts.stream().filter(part -> part.getName().equals(paramPath))
+ .collect(Collectors.toList());
+ return collect;
+ }
+ }
return request.getPart(paramPath);
}
@Override
public void setValue(RestClientRequest clientRequest, Object arg) throws Exception {
- clientRequest.attach(paramPath, (Part) arg);
+ clientRequest.attach(paramPath, arg);
}
@Override
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java
index 8b124ec..fbf56f2 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java
@@ -22,8 +22,10 @@ import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
@@ -52,7 +54,8 @@ public class RestClientRequestImpl implements RestClientRequest {
protected AsyncResponse asyncResp;
- private final Map<String, Part> uploads = new HashMap<>();
+ // maybe part or list of parts
+ private final Map<String, Object> uploads = new HashMap<>();
protected HttpClientRequest request;
@@ -80,7 +83,7 @@ public class RestClientRequestImpl implements RestClientRequest {
}
@Override
- public void attach(String name, Part part) {
+ public void attach(String name, Object part) {
if (null == part) {
LOGGER.debug("null file is ignored, file name = [{}]", name);
return;
@@ -155,35 +158,48 @@ public class RestClientRequestImpl implements RestClientRequest {
}
private void attachFiles(String boundary) {
- Iterator<Entry<String, Part>> uploadsIterator = uploads.entrySet().iterator();
- attachFile(boundary, uploadsIterator);
+ Iterator<Entry<String, Object>> uploadsIterator = uploads.entrySet().iterator();
+ List<NamePartMap> namePartMaps = new ArrayList<>();
+ while (uploadsIterator.hasNext()) {
+ Entry<String, Object> entry = uploadsIterator.next();
+ String name = entry.getKey();
+ Object tmpPart = entry.getValue();
+ if (tmpPart instanceof Part) {
+ namePartMaps.add(new NamePartMap(name, (Part) tmpPart));
+ } else {
+ //it's a list
+ @SuppressWarnings("unchecked")
+ List<Part> partList = (List<Part>) tmpPart;
+ partList.forEach(part -> namePartMaps.add(new NamePartMap(name, part)));
+ }
+ }
+ attachFile(boundary, namePartMaps.iterator());
}
- private void attachFile(String boundary, Iterator<Entry<String, Part>> uploadsIterator) {
+ private void attachFile(String boundary, Iterator<NamePartMap> uploadsIterator) {
if (!uploadsIterator.hasNext()) {
request.write(boundaryEndInfo(boundary));
request.end();
return;
}
- Entry<String, Part> entry = uploadsIterator.next();
+ NamePartMap next = uploadsIterator.next();
// do not use part.getName() to get parameter name
// because pojo consumer not easy to set name to part
- String name = entry.getKey();
- Part part = entry.getValue();
- String filename = part.getSubmittedFileName();
- Buffer fileHeader = fileBoundaryInfo(boundary, name, part);
+ String filename = next.getPart().getSubmittedFileName();
+
+ Buffer fileHeader = fileBoundaryInfo(boundary, next.getName(), next.getPart());
request.write(fileHeader);
- new PumpFromPart(context, part).toWriteStream(request).whenComplete((v, e) -> {
+ new PumpFromPart(context, next.getPart()).toWriteStream(request).whenComplete((v, e) -> {
if (e != null) {
- LOGGER.debug("Failed to sending file [{}:{}].", name, filename, e);
+ LOGGER.debug("Failed to sending file [{}:{}].", next.getName(), filename, e);
asyncResp.consumerFail(e);
return;
}
- LOGGER.debug("finish sending file [{}:{}].", name, filename);
+ LOGGER.debug("finish sending file [{}:{}].", next.getName(), filename);
attachFile(boundary, uploadsIterator);
});
}
@@ -278,4 +294,23 @@ public class RestClientRequestImpl implements RestClientRequest {
public MultiMap getHeaders() {
return request.headers();
}
+
+ public static class NamePartMap {
+ String name;
+
+ Part part;
+
+ public NamePartMap(String name, Part part) {
+ this.name = name;
+ this.part = part;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Part getPart() {
+ return part;
+ }
+ }
}
diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java
index 9274bc2..6280ef1 100644
--- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java
+++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java
@@ -17,6 +17,8 @@
package org.apache.servicecomb.foundation.vertx.http;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@@ -275,6 +277,14 @@ public class VertxServerRequestToHttpServletRequest extends AbstractHttpServletR
return new FileUploadPart(fileUpload);
}
+ @Override
+ public Collection<Part> getParts() {
+ Set<FileUpload> fileUploads = context.fileUploads();
+ List<Part> partList = new ArrayList<>();
+ fileUploads.stream().map(FileUploadPart::new).forEach(partList::add);
+ return partList;
+ }
+
public RoutingContext getContext() {
return context;
}
diff --git a/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestUpload.java b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestUpload.java
new file mode 100644
index 0000000..ed1459a
--- /dev/null
+++ b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestUpload.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.it.testcase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.servicecomb.it.Consumers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.HttpEntity;
+
+public class TestUpload {
+
+ private File file1;
+
+ private File file2;
+
+ private File file3;
+
+ interface UploadIntf {
+
+ }
+
+ private static Consumers<UploadIntf> consumersSpringmvc = new Consumers<>("uploadSpringmvcSchema",
+ UploadIntf.class);
+
+ private static Consumers<UploadIntf> consumersJaxrs = new Consumers<>("uploadJaxrsSchema",
+ UploadIntf.class);
+
+ @Before
+ public void init(){
+ try {
+ file1 = File.createTempFile("jaxrstest1", ".txt");
+ file2 = File.createTempFile("测试", ".txt");
+ file3 = File.createTempFile("files", ".yaml");
+ FileUtils.writeStringToFile(file1, "hello1");
+ FileUtils.writeStringToFile(file2, "中文 2");
+ FileUtils.writeStringToFile(file2, "cse3");
+ } catch (IOException e) {
+ Assert.fail("Failed to create temp file");
+ }
+ }
+
+ @Test
+ public void testJarxUpload1() {
+ Map<String, Object> map = new HashMap<>();
+ map.put("file1", new FileSystemResource(file1));
+ map.put("file2", new FileSystemResource(file2));
+ String upload1 = consumersJaxrs.getSCBRestTemplate().postForObject("upload1", new HttpEntity<>(map), String.class);
+ Assert.assertTrue(constainsAll(upload1, "hello1", "中文 2"));
+ }
+
+ private static boolean constainsAll(String str, String... strings) {
+ for (String string : strings) {
+ if (!str.contains(string)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadJaxrsSchema.java b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadJaxrsSchema.java
new file mode 100644
index 0000000..d70e894
--- /dev/null
+++ b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadJaxrsSchema.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.it.schema;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.servlet.http.Part;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+
+@RestSchema(schemaId = "uploadJaxrsSchema")
+@Path("/v1/uploadJaxrsSchema")
+public class UploadJaxrsSchema {
+ @Path("/upload1")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ public String fileUpload1(@FormParam("file1") Part file1, @FormParam("file2") Part file2) throws IOException {
+ return getStrFromPart(file1) + getStrFromPart(file2);
+ }
+
+ @Path("/uploadArray1")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ public String uploadArray1(@FormParam("file1") Part[] fileArray, @FormParam("file2") Part file2) throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < fileArray.length; i++) {
+ stringBuilder.append(getStrFromPart(fileArray[i]));
+ }
+ return stringBuilder.append(getStrFromPart(file2)).toString();
+ }
+
+
+ @Path("/uploadList1")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ public String uploadList1(@FormParam("file1") List<Part> fileArray, @FormParam("file2") Part file2)
+ throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ fileArray.forEach(part -> {
+ stringBuilder.append(getStrFromPart(part));
+ });
+ return stringBuilder.append(getStrFromPart(file2)).toString();
+ }
+
+
+ @Path("/upload2")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ public String fileUpload2(@FormParam("file1") Part file1, @FormParam("message") String message) throws IOException {
+ return getStrFromPart(file1) + message;
+ }
+
+ @Path("/uploadArray2")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ public String uploadArray2(@FormParam("file1") Part[] fileArray, @FormParam("message") String message)
+ throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < fileArray.length; i++) {
+ stringBuilder.append(getStrFromPart(fileArray[i]));
+ }
+ return stringBuilder.append(message).toString();
+ }
+
+
+ @Path("/uploadList2")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ public String uploadList2(@FormParam("file1") List<Part> fileArray, @FormParam("message") String message)
+ throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ fileArray.forEach(part -> {
+ stringBuilder.append(getStrFromPart(part));
+ });
+ return stringBuilder.append(message).toString();
+ }
+
+ private static String getStrFromPart(Part file1) {
+ try (InputStream is1 = file1.getInputStream()) {
+ return IOUtils.toString(is1);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+}
diff --git a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadSpringmvcSchema.java b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadSpringmvcSchema.java
new file mode 100644
index 0000000..e389d2b
--- /dev/null
+++ b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadSpringmvcSchema.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.it.schema;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.google.common.collect.Lists;
+
+
+@RestSchema(schemaId = "uploadSpringmvcSchema")
+@RequestMapping(path = "/v1/uploadSpringmvcSchema")
+public class UploadSpringmvcSchema {
+
+ @RequestMapping(path = "/upload", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public String fileUpload(@RequestPart(name = "file1") MultipartFile file1,
+ @RequestPart(name = "file2") MultipartFile file2, @RequestAttribute("name") String name) {
+ return _fileUpload(Lists.newArrayList(file1, file2)) + name;
+ }
+
+ @RequestMapping(path = "/uploadArray", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public String fileUploadArray(@RequestPart(name = "file1") MultipartFile[] file1,
+ @RequestPart(name = "file2") MultipartFile[] file2, @RequestAttribute("name") String name) {
+ List<MultipartFile> multipartFileList = new ArrayList<>();
+ multipartFileList.addAll(Arrays.asList(file1));
+ multipartFileList.addAll(Arrays.asList(file2));
+ return _fileUpload(multipartFileList) + name;
+ }
+
+ @RequestMapping(path = "/uploadList", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public String fileUploadList(@RequestPart(name = "file1") List<MultipartFile> file1,
+ @RequestPart(name = "file2") List<MultipartFile> file2, @RequestAttribute("name") String name) {
+ file1.addAll(file2);
+ return _fileUpload(file1) + name;
+ }
+
+ @RequestMapping(path = "/uploadWithoutAnnotation", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public String uploadWithoutAnnotation(MultipartFile file1, MultipartFile file2,
+ @RequestAttribute("name") String name) {
+ return _fileUpload(Lists.newArrayList(file1, file2)) + name;
+ }
+
+ @RequestMapping(path = "/uploadArrayWithoutAnnotation", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public String uploadArrayWithoutAnnotation(MultipartFile[] file1, MultipartFile[] file2,
+ @RequestAttribute("name") String name) {
+ List<MultipartFile> multipartFileList = new ArrayList<>();
+ multipartFileList.addAll(Arrays.asList(file1));
+ multipartFileList.addAll(Arrays.asList(file2));
+ return _fileUpload(multipartFileList) + name;
+ }
+
+ @RequestMapping(path = "/uploadListArrayWithoutAnnotation", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public String uploadListWithoutAnnotation(List<MultipartFile> file1, List<MultipartFile> file2,
+ @RequestAttribute("name") String name) {
+ file1.addAll(file2);
+ return _fileUpload(file1) + name;
+ }
+
+ private static String _fileUpload(List<MultipartFile> fileList) {
+ StringBuilder stringBuilder = new StringBuilder();
+ try {
+ for (MultipartFile multipartFile : fileList) {
+ stringBuilder.append(IOUtils.toString(multipartFile.getBytes(), StandardCharsets.UTF_8.name()));
+ }
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java
index 2d5d75e..b70a921 100644
--- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java
+++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java
@@ -29,13 +29,14 @@ import java.util.Enumeration;
import java.util.List;
import java.util.Map;
-import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.Part;
import javax.ws.rs.core.HttpHeaders;
import org.apache.servicecomb.common.rest.RestConst;
+import org.apache.servicecomb.common.rest.codec.param.FormProcessorCreator.PartProcessor;
+import org.apache.servicecomb.common.rest.definition.RestParam;
import org.apache.servicecomb.foundation.common.part.FilePart;
import org.apache.servicecomb.foundation.common.part.InputStreamPart;
import org.apache.servicecomb.foundation.common.part.ResourcePart;
@@ -48,14 +49,23 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
private Map<String, List<String>> httpHeaders;
+ //contains all the file key in the parts
+ private List<String> fileKeys = new ArrayList<>();
+
// gen by httpHeaders
private Cookie[] cookies;
@SuppressWarnings("unchecked")
public CommonToHttpServletRequest(Map<String, String> pathParams, Map<String, List<String>> queryParams,
- Map<String, List<String>> httpHeaders, Object bodyObject, boolean isFormData) {
+ Map<String, List<String>> httpHeaders, Object bodyObject, boolean isFormData, List<RestParam> paramList) {
setAttribute(RestConst.PATH_PARAMETERS, pathParams);
-
+ if (paramList != null && paramList.size() != 0) {
+ for (RestParam param : paramList) {
+ if (param.getParamProcessor() instanceof PartProcessor) {
+ fileKeys.add(param.getParamName());
+ }
+ }
+ }
if (isFormData) {
setAttribute(RestConst.FORM_PARAMETERS, (Map<String, Object>) bodyObject);
} else {
@@ -66,6 +76,12 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
this.httpHeaders = httpHeaders;
}
+ @SuppressWarnings("unchecked")
+ public CommonToHttpServletRequest(Map<String, String> pathParams, Map<String, List<String>> queryParams,
+ Map<String, List<String>> httpHeaders, Object bodyObject, boolean isFormData) {
+ this(pathParams, queryParams, httpHeaders, bodyObject, isFormData, null);
+ }
+
@Override
public String getContentType() {
return getHeader(HttpHeaders.CONTENT_TYPE);
@@ -164,8 +180,12 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
}
@Override
- public Part getPart(String name) throws IOException, ServletException {
+ public Part getPart(String name) {
Object value = findPartInputValue(name);
+ return getSinglePart(name, value);
+ }
+
+ private Part getSinglePart(String name, Object value) {
if (value == null) {
return null;
}
@@ -196,6 +216,43 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
value.getClass().getName()));
}
+ @Override
+ public Collection<Part> getParts() {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> form = (Map<String, Object>) getAttribute(RestConst.FORM_PARAMETERS);
+ List<Part> partList = new ArrayList<>();
+ filePartListWithForm(partList, form);
+ return partList;
+ }
+
+ private void filePartListWithForm(List<Part> partList, Map<String, Object> form) {
+
+ for (String key : fileKeys) {
+ Object value = form.get(key);
+ if (value == null) {
+ continue;
+ }
+ Class<?> aClass = value.getClass();
+ System.out.println(aClass.isArray());
+ if (Collection.class.isInstance(value)) {
+ Collection<?> collection = (Collection<?>) value;
+ if (collection.isEmpty()) {
+ continue;
+ }
+ for (Object part : collection) {
+ partList.add(getSinglePart(key, part));
+ }
+ } else if (value.getClass().isArray()) {
+ Object[] params = (Object[]) value;
+ for (int i = 0; i < params.length; i++) {
+ partList.add(getSinglePart(key, params[i]));
+ }
+ } else {
+ partList.add(getSinglePart(key, value));
+ }
+ }
+ }
+
protected Object findPartInputValue(String name) {
@SuppressWarnings("unchecked")
Map<String, Object> form = (Map<String, Object>) getAttribute(RestConst.FORM_PARAMETERS);
diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java
index 6f33883..cc6d6d1 100644
--- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java
+++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java
@@ -206,7 +206,8 @@ public class CseClientHttpRequest implements ClientHttpRequest {
protected Object[] collectArguments() {
HttpServletRequest mockRequest = new CommonToHttpServletRequest(requestMeta.getPathParams(), queryParams,
- httpHeaders, requestBody, requestMeta.getSwaggerRestOperation().isFormData());
+ httpHeaders, requestBody, requestMeta.getSwaggerRestOperation().isFormData(),
+ requestMeta.getSwaggerRestOperation().getParamList());
return RestCodec.restToArgs(mockRequest, requestMeta.getSwaggerRestOperation());
}
}
diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java
index 789cdca..1b32f27 100644
--- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java
+++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java
@@ -17,9 +17,12 @@
package org.apache.servicecomb.swagger.generator.core;
+
+import java.lang.reflect.Type;
+
/**
* 通过spi自动注入到所有的GeneratorContext实例中去
*/
public interface CommonParameterTypeProcessor extends ParameterTypeProcessor {
- Class<?> getParameterType();
+ Type getParameterType();
}
diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java
index 8c9a834..3e3e3d9 100644
--- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java
+++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java
@@ -19,6 +19,7 @@ package org.apache.servicecomb.swagger.generator.core;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
@@ -37,6 +38,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
import io.swagger.models.HttpMethod;
import io.swagger.models.Operation;
import io.swagger.models.Path;
@@ -347,6 +350,26 @@ public class OperationGenerator {
if (processor != null) {
processor.process(this, paramIdx);
}
+ Type realType = checkAndGetType(parameterType);
+ if (realType != null) {
+ processor = context.findParameterTypeProcessor(realType);
+ if (processor != null) {
+ processor.process(this, paramIdx);
+ }
+ }
+ }
+
+ // if is list<?> check and get respond type
+ private Type checkAndGetType(Type type) {
+ if (ParameterizedType.class.isAssignableFrom(type.getClass())) {
+ ParameterizedType targetType = (ParameterizedType) type;
+ Class<?> targetCls = (Class<?>) targetType.getRawType();
+ if (List.class.isAssignableFrom(targetCls)) {
+ return TypeFactory.defaultInstance().constructCollectionType(List.class,
+ (Class<?>) targetType.getActualTypeArguments()[0]);
+ }
+ }
+ return null;
}
public void correctOperation() {
diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java
index 3b5bb0d..731d2db 100644
--- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java
+++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java
@@ -17,6 +17,8 @@
package org.apache.servicecomb.swagger.generator.core.processor.parametertype;
+import java.lang.reflect.Type;
+
import javax.servlet.http.HttpServletRequest;
import org.apache.servicecomb.swagger.extend.parameter.HttpRequestParameter;
@@ -25,7 +27,7 @@ import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
public class HttpServletRequestProcessor implements CommonParameterTypeProcessor {
@Override
- public Class<?> getParameterType() {
+ public Type getParameterType() {
return HttpServletRequest.class;
}
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileArrayTypeProcessor.java
similarity index 80%
copy from swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
copy to swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileArrayTypeProcessor.java
index 12e4974..4b040c0 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
+++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileArrayTypeProcessor.java
@@ -17,25 +17,30 @@
package org.apache.servicecomb.swagger.generator.springmvc.processor.parameter;
+import java.lang.reflect.Type;
+
import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.models.parameters.FormParameter;
+import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.FileProperty;
+import io.swagger.models.properties.Property;
-public class MultipartFileTypeProcessor implements CommonParameterTypeProcessor {
+public class MultipartFileArrayTypeProcessor implements CommonParameterTypeProcessor {
@Override
- public Class<?> getParameterType() {
- return MultipartFile.class;
+ public Type getParameterType() {
+ return MultipartFile[].class;
}
@Override
public void process(OperationGenerator operationGenerator, int paramIdx) {
FormParameter parameter = new FormParameter();
- parameter.setType(new FileProperty().getType());
parameter.setName(ParamUtils.getParameterName(operationGenerator.getProviderMethod(), paramIdx));
+ Property property = new ArrayProperty(new FileProperty());
+ parameter.setProperty(property);
operationGenerator.addProviderParameter(parameter);
}
}
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileListTypeProcessor.java
similarity index 75%
copy from swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
copy to swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileListTypeProcessor.java
index 12e4974..3be6a40 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
+++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileListTypeProcessor.java
@@ -17,25 +17,34 @@
package org.apache.servicecomb.swagger.generator.springmvc.processor.parameter;
+import java.lang.reflect.Type;
+import java.util.List;
+
import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
import org.springframework.web.multipart.MultipartFile;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
import io.swagger.models.parameters.FormParameter;
+import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.FileProperty;
+import io.swagger.models.properties.Property;
+
+public class MultipartFileListTypeProcessor implements CommonParameterTypeProcessor {
-public class MultipartFileTypeProcessor implements CommonParameterTypeProcessor {
@Override
- public Class<?> getParameterType() {
- return MultipartFile.class;
+ public Type getParameterType() {
+ return TypeFactory.defaultInstance().constructCollectionType(List.class, MultipartFile.class);
}
@Override
public void process(OperationGenerator operationGenerator, int paramIdx) {
FormParameter parameter = new FormParameter();
- parameter.setType(new FileProperty().getType());
parameter.setName(ParamUtils.getParameterName(operationGenerator.getProviderMethod(), paramIdx));
+ Property property = new ArrayProperty(new FileProperty());
+ parameter.setProperty(property);
operationGenerator.addProviderParameter(parameter);
}
}
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
index 12e4974..8d0972d 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
+++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
@@ -17,6 +17,8 @@
package org.apache.servicecomb.swagger.generator.springmvc.processor.parameter;
+import java.lang.reflect.Type;
+
import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
@@ -27,7 +29,7 @@ import io.swagger.models.properties.FileProperty;
public class MultipartFileTypeProcessor implements CommonParameterTypeProcessor {
@Override
- public Class<?> getParameterType() {
+ public Type getParameterType() {
return MultipartFile.class;
}
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor b/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor
index 0bad0b7..bea33a1 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor
+++ b/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor
@@ -16,3 +16,5 @@
#
org.apache.servicecomb.swagger.generator.springmvc.processor.parameter.MultipartFileTypeProcessor
+org.apache.servicecomb.swagger.generator.springmvc.processor.parameter.MultipartFileArrayTypeProcessor
+org.apache.servicecomb.swagger.generator.springmvc.processor.parameter.MultipartFileListTypeProcessor
\ No newline at end of file
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java
index 94b8570..c0d5e31 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java
@@ -37,6 +37,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
@Component
public class ConverterMgr {
private static final Logger LOGGER = LoggerFactory.getLogger(ConverterMgr.class);
@@ -99,13 +101,40 @@ public class ConverterMgr {
return findCommonConverter(target);
}
+
protected Converter findSrcTarget(Type src, Type target) {
Map<Type, Converter> map = srcTargetMap.get(src);
- if (map == null) {
- return null;
+ if (map != null) {
+ return map.get(target);
+ }
+ Type realSrc = checkAndGetType(src);
+ if (realSrc != null) {
+ Map<Type, Converter> typeConverterMap = srcTargetMap.get(realSrc);
+ if (typeConverterMap != null) {
+ Converter converter = typeConverterMap.get(target);
+ if (converter != null) {
+ return converter;
+ }
+ Type realTarget = checkAndGetType(target);
+ if (realTarget != null) {
+ return typeConverterMap.get(realTarget);
+ }
+ }
}
+ return null;
+ }
- return map.get(target);
+ // if is list<?> check and get respond type
+ private Type checkAndGetType(Type type) {
+ if (ParameterizedType.class.isAssignableFrom(type.getClass())) {
+ ParameterizedType targetType = (ParameterizedType) type;
+ Class<?> targetCls = (Class<?>) targetType.getRawType();
+ if (List.class.isAssignableFrom(targetCls)) {
+ return TypeFactory.defaultInstance().constructCollectionType(List.class,
+ (Class<?>) targetType.getActualTypeArguments()[0]);
+ }
+ }
+ return null;
}
protected Converter findCommonConverter(Type target) {
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java
index c995a93..5b4467c 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java
@@ -17,6 +17,8 @@
package org.apache.servicecomb.swagger.invocation.generator;
+import java.lang.reflect.Type;
+
import org.apache.servicecomb.swagger.extend.parameter.InvocationContextParameter;
import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
@@ -24,7 +26,7 @@ import org.apache.servicecomb.swagger.invocation.context.InvocationContext;
public class InvocationContextProcessor implements CommonParameterTypeProcessor {
@Override
- public Class<?> getParameterType() {
+ public Type getParameterType() {
return InvocationContext.class;
}
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartArrayConverter.java b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartArrayConverter.java
new file mode 100644
index 0000000..54072f0
--- /dev/null
+++ b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartArrayConverter.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.swagger.invocation.converter;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import javax.servlet.http.Part;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+
+@Component
+public class SpringMultipartArrayConverter implements CustomizedConverter {
+
+ @Override
+ public Type getSrcType() {
+ return TypeFactory.defaultInstance().constructCollectionType(List.class, Part.class);
+ }
+
+ @Override
+ public Type getTargetType() {
+ return MultipartFile[].class;
+ }
+
+ @Override
+ public Object convert(Object value) {
+ if (value == null) {
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ List<Part> partList = (List<Part>) value;
+ PartToMultipartFile[] partArray = new PartToMultipartFile[partList.size()];
+ for (int i = 0; i < partArray.length; i++) {
+ partArray[i] = new PartToMultipartFile(partList.get(i));
+ }
+ return partArray;
+ }
+}
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartListConverter.java b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartListConverter.java
new file mode 100644
index 0000000..24236be
--- /dev/null
+++ b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartListConverter.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.swagger.invocation.converter;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.Part;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+@Component
+public class SpringMultipartListConverter implements CustomizedConverter {
+
+
+ @Override
+ public Type getSrcType() {
+ return TypeFactory.defaultInstance().constructCollectionType(List.class, Part.class);
+ }
+
+ @Override
+ public Type getTargetType() {
+ return TypeFactory.defaultInstance().constructCollectionType(List.class, MultipartFile.class);
+ }
+
+ @Override
+ public Object convert(Object value) {
+ if (value == null) {
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ List<Part> partList = (List<Part>) value;
+ List<PartToMultipartFile> fileList = new ArrayList<>();
+ partList.forEach(part -> {
+ fileList.add(new PartToMultipartFile(part));
+ });
+ return fileList;
+ }
+}