From 56f5969b0e347efcb7b7f913851d0a8ea2b9399d Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 1 May 2016 14:56:44 +0300 Subject: [PATCH 01/93] Initial commit --- .gitignore | 212 ++++++++++++++++++ README.md | 73 ++++++ pom.xml | 142 ++++++++++++ .../feign/form/FormDataProcessor.java | 30 +++ .../feign/form/FormEncodedDataProcessor.java | 61 +++++ .../ru/xxlabaza/feign/form/FormEncoder.java | 69 ++++++ .../form/MultipartEncodedDataProcessor.java | 136 +++++++++++ src/test/java/ru/xxlabaza/feign/form/Dto.java | 38 ++++ .../java/ru/xxlabaza/feign/form/Server.java | 97 ++++++++ .../ru/xxlabaza/feign/form/TestClient.java | 129 +++++++++++ src/test/resources/file.txt | 2 + 11 files changed, 989 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java create mode 100644 src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java create mode 100644 src/main/java/ru/xxlabaza/feign/form/FormEncoder.java create mode 100644 src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java create mode 100644 src/test/java/ru/xxlabaza/feign/form/Dto.java create mode 100644 src/test/java/ru/xxlabaza/feign/form/Server.java create mode 100644 src/test/java/ru/xxlabaza/feign/form/TestClient.java create mode 100644 src/test/resources/file.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b4f181789 --- /dev/null +++ b/.gitignore @@ -0,0 +1,212 @@ +# Created by https://www.gitignore.io + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +### Netbeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +nb-configuration.xml +.nb-gradle/ + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### SublimeText ### +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### Linux ### +*~ + +# KDE directory preferences +.directory + + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +/target/ +*.pdfmarks diff --git a/README.md b/README.md new file mode 100644 index 000000000..5db90ad5c --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Form Encoder + +This module adds support for encoding **application/x-www-form-urlencoded** and **multipart/form-data** forms. + +Add `FormEncoder` to your `Feign.Builder` like so: + +```java +SomeApi github = Feign.builder() + .encoder(new FormEncoder()) + .target(SomeApi.class, "http://api.some.org"); +``` + +Moreover, you can decorate the existing encoder, for example JsonEncoder like this: + +```java +SomeApi github = Feign.builder() + .encoder(new FormEncoder(new JacksonEncoder())) + .target(SomeApi.class, "http://api.some.org"); +``` + +And use them together: + +```java +interface SomeApi { + + @RequestLine("POST /json") + @Headers("Content-Type: application/json") + void json (Dto dto); + + @RequestLine("POST /form") + @Headers("Content-Type: application/x-www-form-urlencoded") + void from (@Param("field1") String field1, @Param("field2") String field2); + +} +``` + +You can specify two types of encoding forms by `Content-Type` header. + +### application/x-www-form-urlencoded + +```java +interface SomeApi { + + ... + + @RequestLine("POST /authorization") + @Headers("Content-Type: application/x-www-form-urlencoded") + void authorization (@Param("email") String email, @Param("password") String password); + + ... + +} +``` + +### multipart/form-data + +```java +interface SomeApi { + + ... + + @RequestLine("POST /send_photo") + @Headers("Content-Type: multipart/form-data") + void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") Path photo); + + ... + +} +``` + +In example above, we send file in parameter named **photo** with additional field in form **is_public**. + +> **IMPORTANT:** You can specify your files in API method by declaring type **Path**, **File** or even **byte[]**. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..c9d82a84d --- /dev/null +++ b/pom.xml @@ -0,0 +1,142 @@ + + + + 4.0.0 + + ru.xxlabaza + feign-form + 1.0.0 + jar + + + org.springframework.boot + spring-boot-starter-parent + 1.3.3.RELEASE + + + Netflix Feign Forms + + Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) + + + + + Artem Labazin + artem.labazin@gmail.com + + + 2016 + + + UTF-8 + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + maven-deploy-plugin + 2.8.2 + + internal.repo::default::file://${project.build.directory}/mvn-repo + + + + com.github.github + site-maven-plugin + 0.12 + + Maven artifacts for ${project.version} + true + ${project.build.directory}/mvn-repo + refs/heads/mvn-repo + + **/* + + xxlabaza + feign-form + + + + + site + + deploy + + + + + + + + + internal.repo + Temporary Staging Repository + file://${project.build.directory}/mvn-repo + + + + + + com.netflix.feign + feign-core + 8.16.2 + provided + + + org.projectlombok + lombok + 1.16.8 + compile + + + + org.springframework.boot + spring-boot-starter-web + test + + + com.netflix.feign + feign-jackson + 8.16.2 + test + + + junit + junit + test + + + \ No newline at end of file diff --git a/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java new file mode 100644 index 000000000..866cc7041 --- /dev/null +++ b/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; + +import feign.RequestTemplate; +import java.util.Map; + +/** + * @author Artem Labazin + * @since 30.04.2016 + */ +interface FormDataProcessor { + + void process (Map data, RequestTemplate template); + + String getSupportetContentType (); +} diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java new file mode 100644 index 000000000..93eda5f49 --- /dev/null +++ b/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; + +import feign.RequestTemplate; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import lombok.SneakyThrows; + +import static java.util.stream.Collectors.joining; + +/** + * @author Artem Labazin + * @since 30.04.2016 + */ +class FormEncodedDataProcessor implements FormDataProcessor { + + public static final String CONTENT_TYPE; + + static { + CONTENT_TYPE = "application/x-www-form-urlencoded"; + } + + @Override + public void process (Map data, RequestTemplate template) { + String body = data.entrySet().stream() + .map(this::createKeyValuePair) + .collect(joining("&")); + + template.header("Content-Type", CONTENT_TYPE); + template.body(body); + } + + @Override + public String getSupportetContentType () { + return CONTENT_TYPE; + } + + @SneakyThrows + private String createKeyValuePair (Map.Entry entry) { + return new StringBuilder() + .append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())) + .append('=') + .append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8.name())) + .toString(); + } +} diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java new file mode 100644 index 000000000..035862883 --- /dev/null +++ b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; + +import feign.RequestTemplate; +import feign.codec.Encoder; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Artem Labazin + * @since 30.04.2016 + */ +public class FormEncoder implements Encoder { + + private final Encoder deligate; + + private final Map processors; + + public FormEncoder () { + this(new Encoder.Default()); + } + + public FormEncoder (Encoder deligate) { + this.deligate = deligate; + processors = Stream.of(new FormEncodedDataProcessor(), new MultipartEncodedDataProcessor()) + .collect(Collectors.toMap(FormDataProcessor::getSupportetContentType, Function.identity())); + } + + @Override + public void encode (Object object, Type bodyType, RequestTemplate template) { + if (bodyType != MAP_STRING_WILDCARD) { + deligate.encode(object, bodyType, template); + return; + } + + String formType = template.headers().entrySet().stream() + .filter(entry -> entry.getKey().equals("Content-Type")) + .flatMap(it -> it.getValue().stream()) + .filter(it -> processors.containsKey(it)) + .findFirst() + .orElse(""); + + if (formType.isEmpty()) { + deligate.encode(object, bodyType, template); + return; + } + + @SuppressWarnings("unchecked") + Map data = (Map) object; + processors.get(formType).process(data, template); + } +} diff --git a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java new file mode 100644 index 000000000..408301b73 --- /dev/null +++ b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java @@ -0,0 +1,136 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; + +import feign.RequestTemplate; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import lombok.SneakyThrows; +import lombok.val; + +import static feign.Util.UTF_8; + +/** + * @author Artem Labazin + * @since 30.04.2016 + */ +class MultipartEncodedDataProcessor implements FormDataProcessor { + + public static final String CONTENT_TYPE; + + private static final String CRLF; + + static { + CONTENT_TYPE = "multipart/form-data"; + CRLF = "\r\n"; + } + + @Override + public void process (Map data, RequestTemplate template) { + val boundary = createBoundary(); + val outputStream = new ByteArrayOutputStream(); + val writer = new PrintWriter(outputStream); + + data.entrySet().stream().forEach(it -> { + writer.append("--" + boundary).append(CRLF); + if (isFile(it.getValue())) { + writeFile(outputStream, writer, it.getKey(), it.getValue()); + } else { + writeParameter(writer, it.getKey(), it.getValue().toString()); + } + writer.append(CRLF).flush(); + }); + + writer.append("--" + boundary + "--").append(CRLF).flush(); + + val contentType = new StringBuilder() + .append(CONTENT_TYPE) + .append("; boundary=") + .append(boundary) + .toString(); + + template.header("Content-Type", contentType); + template.body(outputStream.toByteArray(), UTF_8); + } + + @Override + public String getSupportetContentType () { + return CONTENT_TYPE; + } + + private String createBoundary () { + return Long.toHexString(System.currentTimeMillis()); + } + + private boolean isFile (Object value) { + return value != null && (value instanceof Path || value instanceof File || value instanceof byte[]); + } + + private void writeParameter (PrintWriter writer, String name, String value) { + writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(CRLF); + writer.append("Content-Type: text/plain; charset=UTF-8").append(CRLF); + writer.append(CRLF).append(value); + } + + private void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { + if (value instanceof byte[]) { + writeFile(output, writer, name, (byte[]) value); + return; + } + + Path pathValue = value instanceof Path + ? (Path) value + : ((File) value).toPath(); + + writeFile(output, writer, name, pathValue); + } + + @SneakyThrows + private void writeFile (OutputStream output, PrintWriter writer, String name, Path value) { + writeFileMeta(writer, name, value.getFileName().toString()); + Files.copy(value, output); + output.flush(); + } + + @SneakyThrows + private void writeFile (OutputStream output, PrintWriter writer, String name, byte[] bytes) { + writeFileMeta(writer, name, ""); + output.write(bytes); + output.flush(); + } + + private void writeFileMeta (PrintWriter writer, String name, String fileName) { + val contentDesposition = new StringBuilder() + .append("Content-Disposition: form-data; name=\"").append(name).append("\"; ") + .append("filename=\"").append(fileName).append("\"") + .toString(); + val contentType = new StringBuilder() + .append("Content-Type: ") + .append(URLConnection.guessContentTypeFromName(fileName)) + .toString(); + + writer.append(contentDesposition).append(CRLF); + writer.append(contentType).append(CRLF); + writer.append("Content-Transfer-Encoding: binary").append(CRLF); + writer.append(CRLF).flush(); + } +} diff --git a/src/test/java/ru/xxlabaza/feign/form/Dto.java b/src/test/java/ru/xxlabaza/feign/form/Dto.java new file mode 100644 index 000000000..8fc05007f --- /dev/null +++ b/src/test/java/ru/xxlabaza/feign/form/Dto.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Artem Labazin + * @since 01.05.2016 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Dto implements Serializable { + + private static final long serialVersionUID = 4743133513526293872L; + + private String name; + + private Integer age; + +} diff --git a/src/test/java/ru/xxlabaza/feign/form/Server.java b/src/test/java/ru/xxlabaza/feign/form/Server.java new file mode 100644 index 000000000..cffd3c1d9 --- /dev/null +++ b/src/test/java/ru/xxlabaza/feign/form/Server.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; + +import java.util.List; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.multipart.MultipartFile; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CONFLICT; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT; +import static org.springframework.http.HttpStatus.LOCKED; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author Artem Labazin + * @since 30.04.2016 + */ +@Controller +@SpringBootApplication +public class Server { + + @RequestMapping(value = "/form", method = POST) + public ResponseEntity form (@RequestParam("key1") String key1, @RequestParam("key2") String key2) { + HttpStatus status = !key1.equals(key2) + ? BAD_REQUEST + : OK; + return ResponseEntity.status(status).body(null); + } + + @RequestMapping(value = "/upload/{id}", method = POST) + @ResponseStatus(OK) + public ResponseEntity upload (@PathVariable("id") Integer id, @RequestParam("public") Boolean isPublic, + @RequestParam("file") MultipartFile file) { + HttpStatus status; + if (id == null || id != 10) { + status = LOCKED; + } else if (isPublic == null || !isPublic) { + status = FORBIDDEN; + } else if (file.getSize() == 0) { + status = I_AM_A_TEAPOT; + } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { + status = CONFLICT; + } else { + status = OK; + } + return ResponseEntity.status(status).body(file.getSize()); + } + + @RequestMapping(value = "/json", method = POST, consumes = APPLICATION_JSON_VALUE) + public ResponseEntity json (@RequestBody Dto dto) { + HttpStatus status; + if (!dto.getName().equals("Artem")) { + status = CONFLICT; + } else if (!dto.getAge().equals(11)) { + status = I_AM_A_TEAPOT; + } else { + status = OK; + } + return ResponseEntity.status(status).body("ok"); + } + + @RequestMapping(value = "/query_map") + public ResponseEntity queryMap (@RequestParam("filter") List filters) { + HttpStatus status; + if (filters == null || filters.isEmpty()) { + status = BAD_REQUEST; + } else { + status = OK; + } + return ResponseEntity.status(status).body(filters.size()); + } +} diff --git a/src/test/java/ru/xxlabaza/feign/form/TestClient.java b/src/test/java/ru/xxlabaza/feign/form/TestClient.java new file mode 100644 index 000000000..8ddeca8a7 --- /dev/null +++ b/src/test/java/ru/xxlabaza/feign/form/TestClient.java @@ -0,0 +1,129 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; + +import feign.Feign; +import feign.Headers; +import feign.Param; +import feign.QueryMap; +import feign.RequestLine; +import feign.Response; +import feign.jackson.JacksonEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +import static feign.Logger.Level.FULL; + +/** + * @author Artem Labazin + * @since 30.04.2016 + */ +public class TestClient { + + private static ConfigurableApplicationContext context; + + private static final TestApi api; + + static { + api = Feign.builder() + .encoder(new FormEncoder(new JacksonEncoder())) + .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) + .logLevel(FULL) + .target(TestApi.class, "http://localhost:8080"); + } + + @BeforeClass + public static void beforeClass () { + context = SpringApplication.run(Server.class); + } + + @AfterClass + public static void afterClass () { + if (context != null) { + context.close(); + } + } + + @Test + public void testForm () { + Response response = api.form("1", "1"); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.status()); + } + + @Test + public void testFormException () { + Response response = api.form("1", "2"); + + Assert.assertNotNull(response); + Assert.assertEquals(400, response.status()); + } + + @Test + public void testUpload () throws Exception { + Path path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + Assert.assertTrue(Files.exists(path)); + + String response = api.upload(10, Boolean.TRUE, path); + Assert.assertEquals(Files.size(path), Long.parseLong(response)); + } + + @Test + public void testJson () { + Dto dto = new Dto("Artem", 11); + String response = api.json(dto); + + Assert.assertEquals("ok", response); + } + + @Test + public void testQueryMap () { + Map value = Collections.singletonMap("filter", Arrays.asList("one", "two", "three", "four")); + + String response = api.queryMap(value); + + Assert.assertEquals("4", response); + } + + interface TestApi { + + @RequestLine("POST /form") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response form (@Param("key1") String key1, @Param("key2") String key2); + + @RequestLine("POST /upload/{id}") + @Headers("Content-Type: multipart/form-data") + String upload (@Param("id") Integer id, @Param("public") Boolean isPublic, @Param("file") Path file); + + @RequestLine("POST /json") + @Headers("Content-Type: application/json") + String json (Dto dto); + + @RequestLine("POST /query_map") + String queryMap (@QueryMap Map value); + } +} diff --git a/src/test/resources/file.txt b/src/test/resources/file.txt new file mode 100644 index 000000000..178a58b8d --- /dev/null +++ b/src/test/resources/file.txt @@ -0,0 +1,2 @@ + +Hello world! From 8e5280022265624fe5dc21ce38e44d7f9baeb65f Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 1 May 2016 15:10:32 +0300 Subject: [PATCH 02/93] Corrected deploy bug --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index c9d82a84d..1e16996d7 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ UTF-8 1.8 1.8 + github From d3b69e4d8837cf6bdd33e40b86f11ba11caa6034 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 1 May 2016 15:15:17 +0300 Subject: [PATCH 03/93] Added dependency installation to README.md --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 5db90ad5c..36f3ae049 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,36 @@ This module adds support for encoding **application/x-www-form-urlencoded** and **multipart/form-data** forms. +## Add dependency + +Add the following snippet to any project's pom that depends on **feign-form** project: +```xml + + + feign-form + https://raw.github.com/xxlabaza/feign-form/mvn-repo/ + + true + always + + + +``` +Then, include dependency to your project: +```xml + + ... + + ru.xxlabaza + feign-form + 1.0.0 + + ... + +``` + +## Usage + Add `FormEncoder` to your `Feign.Builder` like so: ```java From 6ab1b4c7b0b67a343cdd35523c6f6213d86e1215 Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Mon, 22 Aug 2016 07:44:56 +0200 Subject: [PATCH 04/93] Add support for @FeignClient annotated interfaces. - Autodetect content-type from params to support legacy services. --- README.md | 23 +++++++- pom.xml | 17 +++++- .../ru/xxlabaza/feign/form/FormEncoder.java | 47 +++++++++++++--- .../form/MultipartEncodedDataProcessor.java | 53 ++++++++++++------- .../FeignClientAnnotatedInterfaceTest.java | 38 +++++++++++++ .../feign/form/IMultipartSupportService.java | 26 +++++++++ .../feign/form/MultipartSupportService.java | 28 ++++++++++ .../form/MultipartSupportServiceClient.java | 25 +++++++++ .../java/ru/xxlabaza/feign/form/Server.java | 2 + 9 files changed, 233 insertions(+), 26 deletions(-) create mode 100644 src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java create mode 100644 src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java create mode 100644 src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java create mode 100644 src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java diff --git a/README.md b/README.md index 36f3ae049..63aeaaddf 100644 --- a/README.md +++ b/README.md @@ -100,4 +100,25 @@ interface SomeApi { In example above, we send file in parameter named **photo** with additional field in form **is_public**. -> **IMPORTANT:** You can specify your files in API method by declaring type **Path**, **File** or even **byte[]**. \ No newline at end of file +> **IMPORTANT:** You can specify your files in API method by declaring type **Path**, **File** or even **byte[]**. + +### Spring Cloud Netflix @FeingClient support + +You can also use Form Encoder with `@FeingClient`: + +```java +@FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class) +public interface FileUploadServiceClient extends IFileUploadServiceClient { + + @Configuration + public class MultipartSupportConfig { + + @Bean + @Primary + @Scope("prototype") + public Encoder feignFormEncoder() { + return new FormEncoder(); + } + } +} +``` diff --git a/pom.xml b/pom.xml index 1e16996d7..faca856e0 100644 --- a/pom.xml +++ b/pom.xml @@ -122,7 +122,11 @@ 1.16.8 compile - + + org.springframework + spring-web + compile + org.springframework.boot spring-boot-starter-web @@ -139,5 +143,16 @@ junit test + + org.springframework + spring-test + test + + + org.springframework.cloud + spring-cloud-starter-feign + test + 1.1.5.RELEASE + \ No newline at end of file diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java index 035862883..7259a6cec 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java +++ b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java @@ -18,11 +18,15 @@ import feign.RequestTemplate; import feign.codec.Encoder; import java.lang.reflect.Type; +import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.web.multipart.MultipartFile; + /** * @author Artem Labazin * @since 30.04.2016 @@ -37,15 +41,15 @@ public FormEncoder () { this(new Encoder.Default()); } - public FormEncoder (Encoder deligate) { - this.deligate = deligate; + public FormEncoder (Encoder delegate) { + this.deligate = delegate; processors = Stream.of(new FormEncodedDataProcessor(), new MultipartEncodedDataProcessor()) .collect(Collectors.toMap(FormDataProcessor::getSupportetContentType, Function.identity())); } @Override public void encode (Object object, Type bodyType, RequestTemplate template) { - if (bodyType != MAP_STRING_WILDCARD) { + if (bodyType != MAP_STRING_WILDCARD && !bodyType.equals(MultipartFile.class)) { deligate.encode(object, bodyType, template); return; } @@ -57,13 +61,44 @@ public void encode (Object object, Type bodyType, RequestTemplate template) { .findFirst() .orElse(""); + + if (formType.isEmpty()) { + formType = detectFormType(object, bodyType); + } + if (formType.isEmpty()) { deligate.encode(object, bodyType, template); return; } + + if (object instanceof MultipartFile) { + MultipartFile file = (MultipartFile) object; + Map data = new HashMap<>(); + data.put(file.getName(), object); + processors.get(formType).process(data, template); + } else { + @SuppressWarnings("unchecked") + Map data = (Map) object; + processors.get(formType).process(data, template); + } + } - @SuppressWarnings("unchecked") - Map data = (Map) object; - processors.get(formType).process(data, template); + private String detectFormType(Object object, Type bodyType) { + if (bodyType == MultipartFile.class) { + return MultipartEncodedDataProcessor.CONTENT_TYPE; + } + if (bodyType == MAP_STRING_WILDCARD) { + @SuppressWarnings("unchecked") + Map map = (Map) object; + Optional multipartFile = map.values().stream() + .filter(o -> o instanceof MultipartFile) + .findFirst(); + + if(multipartFile.isPresent()) { + return MultipartEncodedDataProcessor.CONTENT_TYPE; + } + } + + return ""; } } diff --git a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java index 408301b73..43ae84806 100644 --- a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java +++ b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java @@ -15,20 +15,25 @@ */ package ru.xxlabaza.feign.form; -import feign.RequestTemplate; +import static feign.Util.UTF_8; + import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; + +import org.springframework.web.multipart.MultipartFile; + +import feign.RequestTemplate; +import feign.codec.EncodeException; import lombok.SneakyThrows; import lombok.val; -import static feign.Util.UTF_8; - /** * @author Artem Labazin * @since 30.04.2016 @@ -48,19 +53,21 @@ class MultipartEncodedDataProcessor implements FormDataProcessor { public void process (Map data, RequestTemplate template) { val boundary = createBoundary(); val outputStream = new ByteArrayOutputStream(); - val writer = new PrintWriter(outputStream); - - data.entrySet().stream().forEach(it -> { - writer.append("--" + boundary).append(CRLF); - if (isFile(it.getValue())) { - writeFile(outputStream, writer, it.getKey(), it.getValue()); - } else { - writeParameter(writer, it.getKey(), it.getValue().toString()); - } - writer.append(CRLF).flush(); - }); - - writer.append("--" + boundary + "--").append(CRLF).flush(); + + try (val writer = new PrintWriter(outputStream)) { + + data.entrySet().stream().forEach(it -> { + writer.append("--" + boundary).append(CRLF); + if (isFile(it.getValue())) { + writeFile(outputStream, writer, it.getKey(), it.getValue()); + } else { + writeParameter(writer, it.getKey(), it.getValue().toString()); + } + writer.append(CRLF).flush(); + }); + + writer.append("--" + boundary + "--").append(CRLF).flush(); + } val contentType = new StringBuilder() .append(CONTENT_TYPE) @@ -82,7 +89,8 @@ private String createBoundary () { } private boolean isFile (Object value) { - return value != null && (value instanceof Path || value instanceof File || value instanceof byte[]); + return value != null && (value instanceof Path || value instanceof File || value instanceof byte[] + || value instanceof MultipartFile); } private void writeParameter (PrintWriter writer, String name, String value) { @@ -96,6 +104,15 @@ private void writeFile (OutputStream output, PrintWriter writer, String name, Ob writeFile(output, writer, name, (byte[]) value); return; } + + if (value instanceof MultipartFile) { + try { + writeFile(output, writer, name, ((MultipartFile) value).getBytes()); + } catch (IOException e) { + throw new EncodeException("Can't encode MultipartFile", e); + } + return; + } Path pathValue = value instanceof Path ? (Path) value @@ -117,7 +134,7 @@ private void writeFile (OutputStream output, PrintWriter writer, String name, by output.write(bytes); output.flush(); } - + private void writeFileMeta (PrintWriter writer, String name, String fileName) { val contentDesposition = new StringBuilder() .append("Content-Disposition: form-data; name=\"").append(name).append("\"; ") diff --git a/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java b/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java new file mode 100644 index 000000000..2ea9b654d --- /dev/null +++ b/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java @@ -0,0 +1,38 @@ +package ru.xxlabaza.feign.form; + +import java.nio.charset.StandardCharsets; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.multipart.MultipartFile; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Server.class) +@WebIntegrationTest(value = {"server.port=8080", "feign.hystrix.enabled=false"}) +public class FeignClientAnnotatedInterfaceTest { + + @Autowired + private MultipartSupportServiceClient client; + + @Test + public void upload1Test() throws Exception { + + MultipartFile file = new MockMultipartFile("file", "test".getBytes(StandardCharsets.UTF_8)); + String response = client.upload1("test folder", file, "message text"); + Assert.assertEquals("test:message text", response); + } + + @Test + public void upload2Test() throws Exception { + + MultipartFile file = new MockMultipartFile("file", "test".getBytes(StandardCharsets.UTF_8)); + String response = client.upload2(file, "test folder", "message text"); + Assert.assertEquals("test:message text", response); + } +} diff --git a/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java b/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java new file mode 100644 index 000000000..fc67ebde2 --- /dev/null +++ b/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java @@ -0,0 +1,26 @@ +package ru.xxlabaza.feign.form; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +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.RequestPart; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +public interface IMultipartSupportService { + + @RequestMapping(value = "/multipart/upload1/{folder}" , method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody String upload1( + @PathVariable("folder") String folder, + @RequestPart MultipartFile file, + @RequestParam(value = "message", required = false) String message); + + @RequestMapping(value = "/multipart/upload2/{folder}" , method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody String upload2( + @RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message); +} diff --git a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java new file mode 100644 index 000000000..70f2fceef --- /dev/null +++ b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java @@ -0,0 +1,28 @@ +package ru.xxlabaza.feign.form; + +import java.io.IOException; + +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +public class MultipartSupportService implements IMultipartSupportService { + + @Override + public String upload1(String folder, MultipartFile file, String message) { + try { + return new String(file.getBytes()) + ":" + message; + } catch (IOException e) { + throw new RuntimeException("Can't get file content", e); + } + } + + @Override + public String upload2(MultipartFile file, String folder, String message) { + try { + return new String(file.getBytes()) + ":" + message; + } catch (IOException e) { + throw new RuntimeException("Can't get file content", e); + } + } +} diff --git a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java new file mode 100644 index 000000000..c2f69ec71 --- /dev/null +++ b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java @@ -0,0 +1,25 @@ +package ru.xxlabaza.feign.form; + +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; + +import feign.codec.Encoder; + +@FeignClient(name = "multipart-support-service", url = "http://localhost:8080", + configuration = MultipartSupportServiceClient.MultipartSupportConfig.class) +public interface MultipartSupportServiceClient extends IMultipartSupportService { + + @Configuration + public class MultipartSupportConfig { + + @Bean + @Primary + @Scope("prototype") + public Encoder feignFormEncoder() { + return new FormEncoder(); + } + } +} diff --git a/src/test/java/ru/xxlabaza/feign/form/Server.java b/src/test/java/ru/xxlabaza/feign/form/Server.java index cffd3c1d9..287a21cca 100644 --- a/src/test/java/ru/xxlabaza/feign/form/Server.java +++ b/src/test/java/ru/xxlabaza/feign/form/Server.java @@ -17,6 +17,7 @@ import java.util.List; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -42,6 +43,7 @@ */ @Controller @SpringBootApplication +@EnableFeignClients public class Server { @RequestMapping(value = "/form", method = POST) From 5ade5ddca69c0ef34d6d9ad137b45a94e410dfc6 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 6 Sep 2016 13:03:49 +0300 Subject: [PATCH 05/93] - Added Tomasz Juchniewicz as a coauthor of the project; - Updated dependencies versions; - Corrected deprecated annotations in tests; - Style refactoring. --- .gitignore | 1 + pom.xml | 26 +++++++--- .../feign/form/FormDataProcessor.java | 2 +- .../ru/xxlabaza/feign/form/FormEncoder.java | 23 ++++----- .../form/MultipartEncodedDataProcessor.java | 29 +++++------ .../{TestClient.java => BasicClientTest.java} | 34 ++++++------- .../FeignClientAnnotatedInterfaceTest.java | 51 ++++++++++++++----- .../feign/form/IMultipartSupportService.java | 49 ++++++++++++++---- .../feign/form/MultipartSupportService.java | 22 +++++++- .../form/MultipartSupportServiceClient.java | 31 +++++++++-- .../java/ru/xxlabaza/feign/form/Server.java | 30 ++++++----- 11 files changed, 199 insertions(+), 99 deletions(-) rename src/test/java/ru/xxlabaza/feign/form/{TestClient.java => BasicClientTest.java} (86%) diff --git a/.gitignore b/.gitignore index b4f181789..46f6a7410 100644 --- a/.gitignore +++ b/.gitignore @@ -210,3 +210,4 @@ Temporary Items .apdisk /target/ *.pdfmarks +log.txt diff --git a/pom.xml b/pom.xml index faca856e0..5afeec3dc 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ ru.xxlabaza feign-form - 1.0.0 + 1.0.1 jar org.springframework.boot spring-boot-starter-parent - 1.3.3.RELEASE + 1.4.0.RELEASE Netflix Feign Forms @@ -24,7 +24,11 @@ Artem Labazin - artem.labazin@gmail.com + xxlabaza@gmail.com + + + Tomasz Juchniewicz + tjuchniewicz@gmail.com 2016 @@ -54,7 +58,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.3 + 2.10.4 -Xdoclint:none @@ -113,13 +117,13 @@ com.netflix.feign feign-core - 8.16.2 + 8.18.0 provided org.projectlombok lombok - 1.16.8 + 1.16.10 compile @@ -135,10 +139,16 @@ com.netflix.feign feign-jackson - 8.16.2 + 8.18.0 test + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.cloud spring-cloud-starter-feign diff --git a/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java index 866cc7041..10e23f16a 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java +++ b/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java index 7259a6cec..c30c69147 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java +++ b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,16 +15,16 @@ */ package ru.xxlabaza.feign.form; +import static java.util.stream.Collectors.toMap; + import feign.RequestTemplate; import feign.codec.Encoder; import java.lang.reflect.Type; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; - import org.springframework.web.multipart.MultipartFile; /** @@ -44,7 +44,7 @@ public FormEncoder () { public FormEncoder (Encoder delegate) { this.deligate = delegate; processors = Stream.of(new FormEncodedDataProcessor(), new MultipartEncodedDataProcessor()) - .collect(Collectors.toMap(FormDataProcessor::getSupportetContentType, Function.identity())); + .collect(toMap(FormDataProcessor::getSupportetContentType, Function.identity())); } @Override @@ -61,20 +61,19 @@ public void encode (Object object, Type bodyType, RequestTemplate template) { .findFirst() .orElse(""); - + if (formType.isEmpty()) { formType = detectFormType(object, bodyType); } - + if (formType.isEmpty()) { deligate.encode(object, bodyType, template); return; } - + if (object instanceof MultipartFile) { MultipartFile file = (MultipartFile) object; - Map data = new HashMap<>(); - data.put(file.getName(), object); + Map data = Collections.singletonMap(file.getName(), object); processors.get(formType).process(data, template); } else { @SuppressWarnings("unchecked") @@ -93,12 +92,12 @@ private String detectFormType(Object object, Type bodyType) { Optional multipartFile = map.values().stream() .filter(o -> o instanceof MultipartFile) .findFirst(); - + if(multipartFile.isPresent()) { return MultipartEncodedDataProcessor.CONTENT_TYPE; } } - + return ""; } } diff --git a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java index 43ae84806..9953680a8 100644 --- a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java +++ b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,8 @@ import static feign.Util.UTF_8; +import feign.RequestTemplate; +import feign.codec.EncodeException; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -26,13 +28,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; - -import org.springframework.web.multipart.MultipartFile; - -import feign.RequestTemplate; -import feign.codec.EncodeException; import lombok.SneakyThrows; import lombok.val; +import org.springframework.web.multipart.MultipartFile; /** * @author Artem Labazin @@ -53,7 +51,7 @@ class MultipartEncodedDataProcessor implements FormDataProcessor { public void process (Map data, RequestTemplate template) { val boundary = createBoundary(); val outputStream = new ByteArrayOutputStream(); - + try (val writer = new PrintWriter(outputStream)) { data.entrySet().stream().forEach(it -> { @@ -65,7 +63,7 @@ public void process (Map data, RequestTemplate template) { } writer.append(CRLF).flush(); }); - + writer.append("--" + boundary + "--").append(CRLF).flush(); } @@ -89,8 +87,9 @@ private String createBoundary () { } private boolean isFile (Object value) { - return value != null && (value instanceof Path || value instanceof File || value instanceof byte[] - || value instanceof MultipartFile); + return value != null + && (value instanceof Path || value instanceof File || value instanceof byte[] + || value instanceof MultipartFile); } private void writeParameter (PrintWriter writer, String name, String value) { @@ -104,7 +103,7 @@ private void writeFile (OutputStream output, PrintWriter writer, String name, Ob writeFile(output, writer, name, (byte[]) value); return; } - + if (value instanceof MultipartFile) { try { writeFile(output, writer, name, ((MultipartFile) value).getBytes()); @@ -114,9 +113,9 @@ private void writeFile (OutputStream output, PrintWriter writer, String name, Ob return; } - Path pathValue = value instanceof Path - ? (Path) value - : ((File) value).toPath(); + val pathValue = value instanceof Path + ? (Path) value + : ((File) value).toPath(); writeFile(output, writer, name, pathValue); } @@ -134,7 +133,7 @@ private void writeFile (OutputStream output, PrintWriter writer, String name, by output.write(bytes); output.flush(); } - + private void writeFileMeta (PrintWriter writer, String name, String fileName) { val contentDesposition = new StringBuilder() .append("Content-Disposition: form-data; name=\"").append(name).append("\"; ") diff --git a/src/test/java/ru/xxlabaza/feign/form/TestClient.java b/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java similarity index 86% rename from src/test/java/ru/xxlabaza/feign/form/TestClient.java rename to src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java index 8ddeca8a7..0d2ed0014 100644 --- a/src/test/java/ru/xxlabaza/feign/form/TestClient.java +++ b/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java @@ -28,22 +28,30 @@ import java.util.Arrays; import java.util.Collections; import java.util.Map; -import org.junit.AfterClass; import org.junit.Assert; -import org.junit.BeforeClass; import org.junit.Test; -import org.springframework.boot.SpringApplication; -import org.springframework.context.ConfigurableApplicationContext; import static feign.Logger.Level.FULL; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; /** * @author Artem Labazin * @since 30.04.2016 */ -public class TestClient { - - private static ConfigurableApplicationContext context; +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = DEFINED_PORT, + classes = Server.class, + properties = { + "server.port=8080", + "feign.hystrix.enabled=false" + } +) +public class BasicClientTest { private static final TestApi api; @@ -55,18 +63,6 @@ public class TestClient { .target(TestApi.class, "http://localhost:8080"); } - @BeforeClass - public static void beforeClass () { - context = SpringApplication.run(Server.class); - } - - @AfterClass - public static void afterClass () { - if (context != null) { - context.close(); - } - } - @Test public void testForm () { Response response = api.form("1", "1"); diff --git a/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java b/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java index 2ea9b654d..dad942c1c 100644 --- a/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java +++ b/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java @@ -1,38 +1,63 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; -import java.nio.charset.StandardCharsets; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.multipart.MultipartFile; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Server.class) -@WebIntegrationTest(value = {"server.port=8080", "feign.hystrix.enabled=false"}) +/** + * @author Tomasz Juchniewicz + * @since 22.08.2016 + */ +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = DEFINED_PORT, + classes = Server.class, + properties = { + "server.port=8080", + "feign.hystrix.enabled=false" + } +) public class FeignClientAnnotatedInterfaceTest { @Autowired private MultipartSupportServiceClient client; @Test - public void upload1Test() throws Exception { - - MultipartFile file = new MockMultipartFile("file", "test".getBytes(StandardCharsets.UTF_8)); + public void upload1Test () throws Exception { + MultipartFile file = new MockMultipartFile("file", "test".getBytes(UTF_8)); String response = client.upload1("test folder", file, "message text"); + Assert.assertEquals("test:message text", response); } @Test - public void upload2Test() throws Exception { - - MultipartFile file = new MockMultipartFile("file", "test".getBytes(StandardCharsets.UTF_8)); + public void upload2Test () throws Exception { + MultipartFile file = new MockMultipartFile("file", "test".getBytes(UTF_8)); String response = client.upload2(file, "test folder", "message text"); + Assert.assertEquals("test:message text", response); } } diff --git a/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java b/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java index fc67ebde2..0bcb4392e 100644 --- a/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java +++ b/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; import org.springframework.http.MediaType; @@ -10,17 +25,29 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; +/** + * @author Tomasz Juchniewicz + * @since 22.08.2016 + */ public interface IMultipartSupportService { - @RequestMapping(value = "/multipart/upload1/{folder}" , method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody String upload1( - @PathVariable("folder") String folder, - @RequestPart MultipartFile file, - @RequestParam(value = "message", required = false) String message); - - @RequestMapping(value = "/multipart/upload2/{folder}" , method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody String upload2( - @RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message); + @RequestMapping( + value = "/multipart/upload1/{folder}", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseBody + String upload1 (@PathVariable("folder") String folder, + @RequestPart MultipartFile file, + @RequestParam(value = "message", required = false) String message); + + @RequestMapping( + value = "/multipart/upload2/{folder}", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseBody + String upload2 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message); } diff --git a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java index 70f2fceef..f95c94cbf 100644 --- a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java +++ b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java @@ -1,10 +1,28 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; import java.io.IOException; - import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +/** + * @author Tomasz Juchniewicz + * @since 22.08.2016 + */ @RestController public class MultipartSupportService implements IMultipartSupportService { @@ -16,7 +34,7 @@ public String upload1(String folder, MultipartFile file, String message) { throw new RuntimeException("Can't get file content", e); } } - + @Override public String upload2(MultipartFile file, String folder, String message) { try { diff --git a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java index c2f69ec71..8d3c50a71 100644 --- a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java +++ b/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java @@ -1,15 +1,36 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 ru.xxlabaza.feign.form; +import feign.codec.Encoder; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; -import feign.codec.Encoder; - -@FeignClient(name = "multipart-support-service", url = "http://localhost:8080", - configuration = MultipartSupportServiceClient.MultipartSupportConfig.class) +/** + * @author Tomasz Juchniewicz + * @since 22.08.2016 + */ +@FeignClient( + name = "multipart-support-service", + url = "http://localhost:8080", + configuration = MultipartSupportServiceClient.MultipartSupportConfig.class +) public interface MultipartSupportServiceClient extends IMultipartSupportService { @Configuration @@ -18,7 +39,7 @@ public class MultipartSupportConfig { @Bean @Primary @Scope("prototype") - public Encoder feignFormEncoder() { + public Encoder feignFormEncoder () { return new FormEncoder(); } } diff --git a/src/test/java/ru/xxlabaza/feign/form/Server.java b/src/test/java/ru/xxlabaza/feign/form/Server.java index 287a21cca..96b0f3909 100644 --- a/src/test/java/ru/xxlabaza/feign/form/Server.java +++ b/src/test/java/ru/xxlabaza/feign/form/Server.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +15,15 @@ */ package ru.xxlabaza.feign.form; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CONFLICT; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT; +import static org.springframework.http.HttpStatus.LOCKED; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + import java.util.List; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.feign.EnableFeignClients; @@ -28,15 +37,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.multipart.MultipartFile; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.CONFLICT; -import static org.springframework.http.HttpStatus.FORBIDDEN; -import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT; -import static org.springframework.http.HttpStatus.LOCKED; -import static org.springframework.http.HttpStatus.OK; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.web.bind.annotation.RequestMethod.POST; - /** * @author Artem Labazin * @since 30.04.2016 @@ -47,7 +47,9 @@ public class Server { @RequestMapping(value = "/form", method = POST) - public ResponseEntity form (@RequestParam("key1") String key1, @RequestParam("key2") String key2) { + public ResponseEntity form (@RequestParam("key1") String key1, + @RequestParam("key2") String key2 + ) { HttpStatus status = !key1.equals(key2) ? BAD_REQUEST : OK; @@ -56,8 +58,10 @@ public ResponseEntity form (@RequestParam("key1") String key1, @RequestPar @RequestMapping(value = "/upload/{id}", method = POST) @ResponseStatus(OK) - public ResponseEntity upload (@PathVariable("id") Integer id, @RequestParam("public") Boolean isPublic, - @RequestParam("file") MultipartFile file) { + public ResponseEntity upload (@PathVariable("id") Integer id, + @RequestParam("public") Boolean isPublic, + @RequestParam("file") MultipartFile file + ) { HttpStatus status; if (id == null || id != 10) { status = LOCKED; From fa5a56cba88ee29c5a855d65470f3b487eccc638 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 6 Sep 2016 13:06:03 +0300 Subject: [PATCH 06/93] Corrected README.md. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63aeaaddf..92623bd89 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Then, include dependency to your project: ru.xxlabaza feign-form - 1.0.0 + 1.0.1 ... From 5f9ec9e68a15cf7cfe7a597a0ec6019279ee6b03 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 12 Sep 2016 13:01:53 +0300 Subject: [PATCH 07/93] - Added Java 6+ support and removed java.nio.file.Path support; - Updated major version and readme.md file; - Added LICENSE file. https://github.com/xxlabaza/feign-form/issues/4 --- LICENSE | 202 ++++++++++++++++++ README.md | 6 +- pom.xml | 74 ++++--- .../feign/form/FormEncodedDataProcessor.java | 24 ++- .../ru/xxlabaza/feign/form/FormEncoder.java | 56 +++-- .../form/MultipartEncodedDataProcessor.java | 60 ++++-- .../xxlabaza/feign/form/BasicClientTest.java | 30 +-- src/test/java/ru/xxlabaza/feign/form/Dto.java | 4 +- 8 files changed, 356 insertions(+), 100 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..7f8ced0d1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 Netflix, 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. diff --git a/README.md b/README.md index 92623bd89..13df7702e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Then, include dependency to your project: ru.xxlabaza feign-form - 1.0.1 + 2.0.0 ... @@ -91,7 +91,7 @@ interface SomeApi { @RequestLine("POST /send_photo") @Headers("Content-Type: multipart/form-data") - void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") Path photo); + void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") File photo); ... @@ -100,7 +100,7 @@ interface SomeApi { In example above, we send file in parameter named **photo** with additional field in form **is_public**. -> **IMPORTANT:** You can specify your files in API method by declaring type **Path**, **File** or even **byte[]**. +> **IMPORTANT:** You can specify your files in API method by declaring type **File** or **byte[]**. ### Spring Cloud Netflix @FeingClient support diff --git a/pom.xml b/pom.xml index 5afeec3dc..d2556ba4f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ ru.xxlabaza feign-form - 1.0.1 + 2.0.0 jar @@ -16,10 +16,12 @@ 1.4.0.RELEASE - Netflix Feign Forms + Open Feign Forms Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) + https://github.com/xxlabaza/feign-form + 2016 @@ -31,12 +33,34 @@ tjuchniewicz@gmail.com - 2016 + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + internal.repo + Temporary Staging Repository + file://${project.build.directory}/mvn-repo + + + + + Github + https://github.com/xxlabaza/feign-form/issues + UTF-8 - 1.8 - 1.8 + 1.6 + 1.6 + 1.8 + 1.8 github @@ -102,17 +126,30 @@ + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.15 + + + signature-check + verify + + check + + + + + + org.codehaus.mojo.signature + java16 + 1.0 + + + - - - internal.repo - Temporary Staging Repository - file://${project.build.directory}/mvn-repo - - - com.netflix.feign @@ -147,17 +184,6 @@ org.springframework.boot spring-boot-starter-test - - org.springframework.cloud spring-cloud-starter-feign diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java index 93eda5f49..7a60fa3dc 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java +++ b/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,13 +15,13 @@ */ package ru.xxlabaza.feign.form; +import static feign.Util.UTF_8; + import feign.RequestTemplate; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.Map; import lombok.SneakyThrows; - -import static java.util.stream.Collectors.joining; +import lombok.val; /** * @author Artem Labazin @@ -37,12 +37,16 @@ class FormEncodedDataProcessor implements FormDataProcessor { @Override public void process (Map data, RequestTemplate template) { - String body = data.entrySet().stream() - .map(this::createKeyValuePair) - .collect(joining("&")); + val body = new StringBuilder(); + for (Map.Entry entry : data.entrySet()) { + if (body.length() > 0) { + body.append('&'); + } + body.append(createKeyValuePair(entry)); + } template.header("Content-Type", CONTENT_TYPE); - template.body(body); + template.body(body.toString()); } @Override @@ -53,9 +57,9 @@ public String getSupportetContentType () { @SneakyThrows private String createKeyValuePair (Map.Entry entry) { return new StringBuilder() - .append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())) + .append(URLEncoder.encode(entry.getKey(), UTF_8.name())) .append('=') - .append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8.name())) + .append(URLEncoder.encode(entry.getValue().toString(), UTF_8.name())) .toString(); } } diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java index c30c69147..a11be40d4 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java +++ b/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java @@ -15,16 +15,14 @@ */ package ru.xxlabaza.feign.form; -import static java.util.stream.Collectors.toMap; - import feign.RequestTemplate; import feign.codec.Encoder; import java.lang.reflect.Type; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Stream; +import lombok.val; import org.springframework.web.multipart.MultipartFile; /** @@ -43,8 +41,15 @@ public FormEncoder () { public FormEncoder (Encoder delegate) { this.deligate = delegate; - processors = Stream.of(new FormEncodedDataProcessor(), new MultipartEncodedDataProcessor()) - .collect(toMap(FormDataProcessor::getSupportetContentType, Function.identity())); + processors = new HashMap(2, 1.F); + + val formEncodedDataProcessor = new FormEncodedDataProcessor(); + processors.put(formEncodedDataProcessor.getSupportetContentType(), + formEncodedDataProcessor); + + val multipartEncodedDataProcessor = new MultipartEncodedDataProcessor(); + processors.put(multipartEncodedDataProcessor.getSupportetContentType(), + multipartEncodedDataProcessor); } @Override @@ -54,13 +59,21 @@ public void encode (Object object, Type bodyType, RequestTemplate template) { return; } - String formType = template.headers().entrySet().stream() - .filter(entry -> entry.getKey().equals("Content-Type")) - .flatMap(it -> it.getValue().stream()) - .filter(it -> processors.containsKey(it)) - .findFirst() - .orElse(""); - + String formType = ""; + for (Map.Entry> entry : template.headers().entrySet()) { + if (!entry.getKey().equals("Content-Type")) { + continue; + } + for (String contentType : entry.getValue()) { + if (processors.containsKey(contentType)) { + formType = contentType; + break; + } + } + if (!formType.isEmpty()) { + break; + } + } if (formType.isEmpty()) { formType = detectFormType(object, bodyType); @@ -82,19 +95,16 @@ public void encode (Object object, Type bodyType, RequestTemplate template) { } } - private String detectFormType(Object object, Type bodyType) { + @SuppressWarnings("unchecked") + private String detectFormType (Object object, Type bodyType) { if (bodyType == MultipartFile.class) { return MultipartEncodedDataProcessor.CONTENT_TYPE; } if (bodyType == MAP_STRING_WILDCARD) { - @SuppressWarnings("unchecked") - Map map = (Map) object; - Optional multipartFile = map.values().stream() - .filter(o -> o instanceof MultipartFile) - .findFirst(); - - if(multipartFile.isPresent()) { - return MultipartEncodedDataProcessor.CONTENT_TYPE; + for (Object value : ((Map) object).values()) { + if (value instanceof MultipartFile) { + return MultipartEncodedDataProcessor.CONTENT_TYPE; + } } } diff --git a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java index 9953680a8..4915db384 100644 --- a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java +++ b/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java @@ -21,12 +21,12 @@ import feign.codec.EncodeException; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.URLConnection; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Map; import lombok.SneakyThrows; import lombok.val; @@ -48,23 +48,30 @@ class MultipartEncodedDataProcessor implements FormDataProcessor { } @Override + @SneakyThrows public void process (Map data, RequestTemplate template) { val boundary = createBoundary(); val outputStream = new ByteArrayOutputStream(); - try (val writer = new PrintWriter(outputStream)) { - - data.entrySet().stream().forEach(it -> { + try { + val writer = new PrintWriter(outputStream); + for (Map.Entry entry : data.entrySet()) { writer.append("--" + boundary).append(CRLF); - if (isFile(it.getValue())) { - writeFile(outputStream, writer, it.getKey(), it.getValue()); + if (isFile(entry.getValue())) { + writeFile(outputStream, writer, entry.getKey(), entry.getValue()); } else { - writeParameter(writer, it.getKey(), it.getValue().toString()); + writeParameter(writer, entry.getKey(), entry.getValue().toString()); } writer.append(CRLF).flush(); - }); + } writer.append("--" + boundary + "--").append(CRLF).flush(); + } catch (Throwable throwable) { + try { + outputStream.close(); + } catch (IOException ex) { + } + throw throwable; } val contentType = new StringBuilder() @@ -75,6 +82,7 @@ public void process (Map data, RequestTemplate template) { template.header("Content-Type", contentType); template.body(outputStream.toByteArray(), UTF_8); + outputStream.close(); } @Override @@ -87,9 +95,8 @@ private String createBoundary () { } private boolean isFile (Object value) { - return value != null - && (value instanceof Path || value instanceof File || value instanceof byte[] - || value instanceof MultipartFile); + return value != null && + (value instanceof File || value instanceof byte[] || value instanceof MultipartFile); } private void writeParameter (PrintWriter writer, String name, String value) { @@ -113,25 +120,34 @@ private void writeFile (OutputStream output, PrintWriter writer, String name, Ob return; } - val pathValue = value instanceof Path - ? (Path) value - : ((File) value).toPath(); - - writeFile(output, writer, name, pathValue); + writeFile(output, writer, name, (File) value); } @SneakyThrows - private void writeFile (OutputStream output, PrintWriter writer, String name, Path value) { - writeFileMeta(writer, name, value.getFileName().toString()); - Files.copy(value, output); - output.flush(); + private void writeFile (OutputStream output, PrintWriter writer, String name, File file) { + writeFileMeta(writer, name, file.getName()); + + InputStream input = null; + try { + input = new FileInputStream(file); + byte[] buffer = new byte[1024]; + int length; + while ((length = input.read(buffer)) > 0) { + output.write(buffer, 0, length); + } + } finally { + if (input != null) { + input.close(); + } + } + writer.flush(); } @SneakyThrows private void writeFile (OutputStream output, PrintWriter writer, String name, byte[] bytes) { writeFileMeta(writer, name, ""); output.write(bytes); - output.flush(); + writer.flush(); } private void writeFileMeta (PrintWriter writer, String name, String fileName) { diff --git a/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java b/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java index 0d2ed0014..12912481b 100644 --- a/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java +++ b/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java @@ -23,7 +23,6 @@ import feign.Response; import feign.jackson.JacksonEncoder; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; @@ -32,6 +31,8 @@ import org.junit.Test; import static feign.Logger.Level.FULL; +import java.io.File; +import lombok.val; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import org.junit.runner.RunWith; @@ -47,8 +48,8 @@ webEnvironment = DEFINED_PORT, classes = Server.class, properties = { - "server.port=8080", - "feign.hystrix.enabled=false" + "server.port=8080", + "feign.hystrix.enabled=false" } ) public class BasicClientTest { @@ -65,7 +66,7 @@ public class BasicClientTest { @Test public void testForm () { - Response response = api.form("1", "1"); + val response = api.form("1", "1"); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); @@ -73,7 +74,7 @@ public void testForm () { @Test public void testFormException () { - Response response = api.form("1", "2"); + val response = api.form("1", "2"); Assert.assertNotNull(response); Assert.assertEquals(400, response.status()); @@ -81,28 +82,27 @@ public void testFormException () { @Test public void testUpload () throws Exception { - Path path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); - String response = api.upload(10, Boolean.TRUE, path); - Assert.assertEquals(Files.size(path), Long.parseLong(response)); + val stringResponse = api.upload(10, Boolean.TRUE, path.toFile()); + Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); } @Test public void testJson () { - Dto dto = new Dto("Artem", 11); - String response = api.json(dto); + val dto = new Dto("Artem", 11); + val stringResponse = api.json(dto); - Assert.assertEquals("ok", response); + Assert.assertEquals("ok", stringResponse); } @Test public void testQueryMap () { Map value = Collections.singletonMap("filter", Arrays.asList("one", "two", "three", "four")); - String response = api.queryMap(value); - - Assert.assertEquals("4", response); + val stringResponse = api.queryMap(value); + Assert.assertEquals("4", stringResponse); } interface TestApi { @@ -113,7 +113,7 @@ interface TestApi { @RequestLine("POST /upload/{id}") @Headers("Content-Type: multipart/form-data") - String upload (@Param("id") Integer id, @Param("public") Boolean isPublic, @Param("file") Path file); + String upload (@Param("id") Integer id, @Param("public") Boolean isPublic, @Param("file") File file); @RequestLine("POST /json") @Headers("Content-Type: application/json") diff --git a/src/test/java/ru/xxlabaza/feign/form/Dto.java b/src/test/java/ru/xxlabaza/feign/form/Dto.java index 8fc05007f..3a886d78c 100644 --- a/src/test/java/ru/xxlabaza/feign/form/Dto.java +++ b/src/test/java/ru/xxlabaza/feign/form/Dto.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,5 @@ public class Dto implements Serializable { private static final long serialVersionUID = 4743133513526293872L; private String name; - private Integer age; - } From 6f876c42c8722ab86387a8c707c35eb51e2efbb9 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Thu, 15 Sep 2016 16:49:36 +0300 Subject: [PATCH 08/93] - Moved from custom group id to OpenFeign's; - Corrected readme. --- README.md | 2 +- pom.xml | 2 +- .../java/{ru/xxlabaza => }/feign/form/FormDataProcessor.java | 2 +- .../{ru/xxlabaza => }/feign/form/FormEncodedDataProcessor.java | 2 +- src/main/java/{ru/xxlabaza => }/feign/form/FormEncoder.java | 2 +- .../feign/form/MultipartEncodedDataProcessor.java | 2 +- .../java/{ru/xxlabaza => }/feign/form/BasicClientTest.java | 3 ++- src/test/java/{ru/xxlabaza => }/feign/form/Dto.java | 2 +- .../feign/form/FeignClientAnnotatedInterfaceTest.java | 2 +- .../{ru/xxlabaza => }/feign/form/IMultipartSupportService.java | 2 +- .../{ru/xxlabaza => }/feign/form/MultipartSupportService.java | 2 +- .../feign/form/MultipartSupportServiceClient.java | 3 ++- src/test/java/{ru/xxlabaza => }/feign/form/Server.java | 2 +- 13 files changed, 15 insertions(+), 13 deletions(-) rename src/main/java/{ru/xxlabaza => }/feign/form/FormDataProcessor.java (96%) rename src/main/java/{ru/xxlabaza => }/feign/form/FormEncodedDataProcessor.java (98%) rename src/main/java/{ru/xxlabaza => }/feign/form/FormEncoder.java (99%) rename src/main/java/{ru/xxlabaza => }/feign/form/MultipartEncodedDataProcessor.java (99%) rename src/test/java/{ru/xxlabaza => }/feign/form/BasicClientTest.java (98%) rename src/test/java/{ru/xxlabaza => }/feign/form/Dto.java (96%) rename src/test/java/{ru/xxlabaza => }/feign/form/FeignClientAnnotatedInterfaceTest.java (98%) rename src/test/java/{ru/xxlabaza => }/feign/form/IMultipartSupportService.java (98%) rename src/test/java/{ru/xxlabaza => }/feign/form/MultipartSupportService.java (97%) rename src/test/java/{ru/xxlabaza => }/feign/form/MultipartSupportServiceClient.java (96%) rename src/test/java/{ru/xxlabaza => }/feign/form/Server.java (99%) diff --git a/README.md b/README.md index 13df7702e..9f834424a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Then, include dependency to your project: ... - ru.xxlabaza + io.github.openfeign.form feign-form 2.0.0 diff --git a/pom.xml b/pom.xml index d2556ba4f..9497c8bc4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - ru.xxlabaza + io.github.openfeign.form feign-form 2.0.0 jar diff --git a/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java b/src/main/java/feign/form/FormDataProcessor.java similarity index 96% rename from src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java rename to src/main/java/feign/form/FormDataProcessor.java index 10e23f16a..5bf810fe8 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormDataProcessor.java +++ b/src/main/java/feign/form/FormDataProcessor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import feign.RequestTemplate; import java.util.Map; diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java b/src/main/java/feign/form/FormEncodedDataProcessor.java similarity index 98% rename from src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java rename to src/main/java/feign/form/FormEncodedDataProcessor.java index 7a60fa3dc..28448e633 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormEncodedDataProcessor.java +++ b/src/main/java/feign/form/FormEncodedDataProcessor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import static feign.Util.UTF_8; diff --git a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java b/src/main/java/feign/form/FormEncoder.java similarity index 99% rename from src/main/java/ru/xxlabaza/feign/form/FormEncoder.java rename to src/main/java/feign/form/FormEncoder.java index a11be40d4..07d5d7827 100644 --- a/src/main/java/ru/xxlabaza/feign/form/FormEncoder.java +++ b/src/main/java/feign/form/FormEncoder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import feign.RequestTemplate; import feign.codec.Encoder; diff --git a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java b/src/main/java/feign/form/MultipartEncodedDataProcessor.java similarity index 99% rename from src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java rename to src/main/java/feign/form/MultipartEncodedDataProcessor.java index 4915db384..9de84693c 100644 --- a/src/main/java/ru/xxlabaza/feign/form/MultipartEncodedDataProcessor.java +++ b/src/main/java/feign/form/MultipartEncodedDataProcessor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import static feign.Util.UTF_8; diff --git a/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java b/src/test/java/feign/form/BasicClientTest.java similarity index 98% rename from src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java rename to src/test/java/feign/form/BasicClientTest.java index 12912481b..b743bacd1 100644 --- a/src/test/java/ru/xxlabaza/feign/form/BasicClientTest.java +++ b/src/test/java/feign/form/BasicClientTest.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; +import feign.form.FormEncoder; import feign.Feign; import feign.Headers; import feign.Param; diff --git a/src/test/java/ru/xxlabaza/feign/form/Dto.java b/src/test/java/feign/form/Dto.java similarity index 96% rename from src/test/java/ru/xxlabaza/feign/form/Dto.java rename to src/test/java/feign/form/Dto.java index 3a886d78c..aee60a3eb 100644 --- a/src/test/java/ru/xxlabaza/feign/form/Dto.java +++ b/src/test/java/feign/form/Dto.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import java.io.Serializable; import lombok.AllArgsConstructor; diff --git a/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java b/src/test/java/feign/form/FeignClientAnnotatedInterfaceTest.java similarity index 98% rename from src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java rename to src/test/java/feign/form/FeignClientAnnotatedInterfaceTest.java index dad942c1c..0b129ca07 100644 --- a/src/test/java/ru/xxlabaza/feign/form/FeignClientAnnotatedInterfaceTest.java +++ b/src/test/java/feign/form/FeignClientAnnotatedInterfaceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import static java.nio.charset.StandardCharsets.UTF_8; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; diff --git a/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java b/src/test/java/feign/form/IMultipartSupportService.java similarity index 98% rename from src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java rename to src/test/java/feign/form/IMultipartSupportService.java index 0bcb4392e..c01f14b4d 100644 --- a/src/test/java/ru/xxlabaza/feign/form/IMultipartSupportService.java +++ b/src/test/java/feign/form/IMultipartSupportService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; diff --git a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java b/src/test/java/feign/form/MultipartSupportService.java similarity index 97% rename from src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java rename to src/test/java/feign/form/MultipartSupportService.java index f95c94cbf..2657597f2 100644 --- a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportService.java +++ b/src/test/java/feign/form/MultipartSupportService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import java.io.IOException; import org.springframework.web.bind.annotation.RestController; diff --git a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java b/src/test/java/feign/form/MultipartSupportServiceClient.java similarity index 96% rename from src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java rename to src/test/java/feign/form/MultipartSupportServiceClient.java index 8d3c50a71..d5904c880 100644 --- a/src/test/java/ru/xxlabaza/feign/form/MultipartSupportServiceClient.java +++ b/src/test/java/feign/form/MultipartSupportServiceClient.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; +import feign.form.FormEncoder; import feign.codec.Encoder; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.context.annotation.Bean; diff --git a/src/test/java/ru/xxlabaza/feign/form/Server.java b/src/test/java/feign/form/Server.java similarity index 99% rename from src/test/java/ru/xxlabaza/feign/form/Server.java rename to src/test/java/feign/form/Server.java index 96b0f3909..06ae7d079 100644 --- a/src/test/java/ru/xxlabaza/feign/form/Server.java +++ b/src/test/java/feign/form/Server.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.xxlabaza.feign.form; +package feign.form; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CONFLICT; From dbf89218523e18f24174b8b97ab5ebdc568f6f8f Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Thu, 15 Sep 2016 16:57:39 +0300 Subject: [PATCH 09/93] Corrected pom's URLs --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9497c8bc4..c0d2089ed 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) - https://github.com/xxlabaza/feign-form + https://github.com/OpenFeign/feign-form 2016 @@ -52,7 +52,7 @@ Github - https://github.com/xxlabaza/feign-form/issues + https://github.com/OpenFeign/feign-form/issues From 1a61c806f0e925b26bd515085b6142c1a2bafc4f Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Thu, 15 Sep 2016 18:02:50 +0300 Subject: [PATCH 10/93] Corrected readme and pom --- README.md | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9f834424a..34ad3f37c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Add the following snippet to any project's pom that depends on **feign-form** pr feign-form - https://raw.github.com/xxlabaza/feign-form/mvn-repo/ + https://raw.github.com/OpenFeign/feign-form/mvn-repo/ true always diff --git a/pom.xml b/pom.xml index c0d2089ed..6df0416c6 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.github.openfeign.form feign-form - 2.0.0 + 2.0.1 jar @@ -114,7 +114,7 @@ **/* - xxlabaza + OpenFeign feign-form From 3f1892014234acb27986aaeba2cbc9250311d5dc Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Fri, 16 Sep 2016 16:58:15 +0200 Subject: [PATCH 11/93] Extract support for MultipartFile to separate module. Fixes gh-3. --- .gitignore | 202 ++++-------------- feign-form-spring/pom.xml | 38 ++++ .../feign/form/spring/SpringFormEncoder.java | 44 ++++ .../SpringMultipartEncodedDataProcessor.java | 54 +++++ .../FeignClientAnnotatedInterfaceTest.java | 4 +- .../spring}/IMultipartSupportService.java | 3 +- .../spring}/MultipartSupportService.java | 7 +- .../MultipartSupportServiceClient.java | 9 +- feign-form/pom.xml | 18 ++ .../java/feign/form/FormDataProcessor.java | 2 +- .../feign/form/FormEncodedDataProcessor.java | 2 +- .../main/java/feign/form/FormEncoder.java | 41 +--- .../form/MultipartEncodedDataProcessor.java | 24 +-- .../test/java/feign/form/BasicClientTest.java | 8 +- .../src}/test/java/feign/form/Dto.java | 0 .../src}/test/java/feign/form/Server.java | 2 - .../src}/test/resources/file.txt | 0 pom.xml | 22 +- 18 files changed, 238 insertions(+), 242 deletions(-) create mode 100644 feign-form-spring/pom.xml create mode 100644 feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java create mode 100644 feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java rename {src/test/java/feign/form => feign-form-spring/src/test/java/feign/form/feign/spring}/FeignClientAnnotatedInterfaceTest.java (96%) rename {src/test/java/feign/form => feign-form-spring/src/test/java/feign/form/feign/spring}/IMultipartSupportService.java (98%) rename {src/test/java/feign/form => feign-form-spring/src/test/java/feign/form/feign/spring}/MultipartSupportService.java (87%) rename {src/test/java/feign/form => feign-form-spring/src/test/java/feign/form/feign/spring}/MultipartSupportServiceClient.java (89%) create mode 100644 feign-form/pom.xml rename {src => feign-form/src}/main/java/feign/form/FormDataProcessor.java (95%) rename {src => feign-form/src}/main/java/feign/form/FormEncodedDataProcessor.java (96%) rename {src => feign-form/src}/main/java/feign/form/FormEncoder.java (66%) rename {src => feign-form/src}/main/java/feign/form/MultipartEncodedDataProcessor.java (86%) rename {src => feign-form/src}/test/java/feign/form/BasicClientTest.java (96%) rename {src => feign-form/src}/test/java/feign/form/Dto.java (100%) rename {src => feign-form/src}/test/java/feign/form/Server.java (97%) rename {src => feign-form/src}/test/resources/file.txt (100%) diff --git a/.gitignore b/.gitignore index 46f6a7410..0171a67e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,104 +1,18 @@ -# Created by https://www.gitignore.io +generated -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm +*.class -*.iml - -## Directory-based project format: -.idea/ -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml - -## File-based project format: -*.ipr -*.iws - -## Plugin-specific files: - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml +# Mobile Tools for Java (J2ME) +.mtj.tmp/ -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties +# Package Files # +*.jar +*.war +*.ear +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* -### WebStorm ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm - -*.iml - -## Directory-based project format: -.idea/ -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml - -## File-based project format: -*.ipr -*.iws - -## Plugin-specific files: - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties - - -### Eclipse ### *.pydevproject .metadata .gradle @@ -112,15 +26,8 @@ local.properties .settings/ .loadpath -### Netbeans ### -nbproject/private/ -build/ -nbbuild/ -dist/ -nbdist/ -nbactions.xml -nb-configuration.xml -.nb-gradle/ +# Eclipse Core +.project # External tool builders .externalToolBuilders/ @@ -131,6 +38,12 @@ nb-configuration.xml # CDT-specific .cproject +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + # PDT-specific .buildpath @@ -140,25 +53,9 @@ nb-configuration.xml # TeXlipse plugin .texlipse +# STS (Spring Tool Suite) +.springBeans -### SublimeText ### -# cache files for sublime text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache - -# workspace files are user-specific -*.sublime-workspace - -# project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using SublimeText -# *.sublime-project - -# sftp configuration file -sftp-config.json - - -### Windows ### # Windows image file caches Thumbs.db ehthumbs.db @@ -174,40 +71,29 @@ $RECYCLE.BIN/ *.msi *.msm *.msp - # Windows shortcuts *.lnk - - -### Linux ### -*~ - -# KDE directory preferences -.directory - - -### OSX ### -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear on external disk -.Spotlight-V100 -.Trashes - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk -/target/ -*.pdfmarks -log.txt +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +/.project +/.settings/ +/.classpath +*.classpath +*.project +*.classpath +*.project +/.apt_generated/ + +# IntelliJ Idea +.idea/ +*.iml +*.ipr +*.iws +out/ diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml new file mode 100644 index 000000000..d00a6d6ff --- /dev/null +++ b/feign-form-spring/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + jar + + feign-form-spring + + + io.github.openfeign.form + feign-form-parent + 2.0.1 + + + Open Feign Forms Extension for Spring + + + + ru.xxlabaza + feign-form + 2.0.0 + compile + + + org.springframework + spring-web + compile + + + org.springframework.cloud + spring-cloud-starter-feign + test + 1.1.5.RELEASE + + + \ No newline at end of file diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java new file mode 100644 index 000000000..3ccbe29c8 --- /dev/null +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java @@ -0,0 +1,44 @@ +package feign.form.spring; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Map; + +import org.springframework.web.multipart.MultipartFile; + +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; +import feign.form.FormEncoder; + +/** + * Adds support for {@link MultipartFile} type to {@link FormEncoder}. + * + * @author Tomasz Juchniewicz + * @since 14.09.2016 + */ +public class SpringFormEncoder extends FormEncoder { + + private final Encoder delegate; + + public SpringFormEncoder () { + this(new Encoder.Default()); + } + + public SpringFormEncoder(Encoder delegate) { + this.delegate = delegate; + } + + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + if (!bodyType.equals(MultipartFile.class)) { + delegate.encode(object, bodyType, template); + return; + } + + MultipartFile file = (MultipartFile) object; + Map data = Collections.singletonMap(file.getName(), object); + new SpringMultipartEncodedDataProcessor().process(data, template); + } + +} diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java new file mode 100644 index 000000000..d5923d19b --- /dev/null +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Artem Labazin . + * + * 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 feign.form.spring; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; + +import org.springframework.web.multipart.MultipartFile; + +import feign.codec.EncodeException; +import feign.form.MultipartEncodedDataProcessor; + +/** + * Adds support for {@link MultipartFile} type to {@link MultipartEncodedDataProcessor}. + * + * @author Tomasz Juchniewicz + * @since 14.09.2016 + */ +public class SpringMultipartEncodedDataProcessor extends MultipartEncodedDataProcessor { + + + @Override + protected boolean isFile (Object value) { + return super.isFile(value) || value instanceof MultipartFile; + } + + @Override + protected void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { + if (value instanceof MultipartFile) { + try { + writeFile(output, writer, name, ((MultipartFile) value).getBytes()); + } catch (IOException e) { + throw new EncodeException("Can't encode MultipartFile", e); + } + return; + } + + super.writeFile(output, writer, name, value); + } +} diff --git a/src/test/java/feign/form/FeignClientAnnotatedInterfaceTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java similarity index 96% rename from src/test/java/feign/form/FeignClientAnnotatedInterfaceTest.java rename to feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java index 0b129ca07..7cf666b3c 100644 --- a/src/test/java/feign/form/FeignClientAnnotatedInterfaceTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feign.form; +package feign.form.feign.spring; import static java.nio.charset.StandardCharsets.UTF_8; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; @@ -34,7 +34,7 @@ @RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = DEFINED_PORT, - classes = Server.class, + classes = MultipartSupportService.class, properties = { "server.port=8080", "feign.hystrix.enabled=false" diff --git a/src/test/java/feign/form/IMultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java similarity index 98% rename from src/test/java/feign/form/IMultipartSupportService.java rename to feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java index c01f14b4d..79de3a4f7 100644 --- a/src/test/java/feign/form/IMultipartSupportService.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feign.form; +package feign.form.feign.spring; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; @@ -50,4 +50,5 @@ String upload1 (@PathVariable("folder") String folder, String upload2 (@RequestBody MultipartFile file, @PathVariable("folder") String folder, @RequestParam(value = "message", required = false) String message); + } diff --git a/src/test/java/feign/form/MultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java similarity index 87% rename from src/test/java/feign/form/MultipartSupportService.java rename to feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java index 2657597f2..bcb04d4e1 100644 --- a/src/test/java/feign/form/MultipartSupportService.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java @@ -13,9 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feign.form; +package feign.form.feign.spring; import java.io.IOException; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -23,7 +26,9 @@ * @author Tomasz Juchniewicz * @since 22.08.2016 */ +@SpringBootApplication @RestController +@EnableFeignClients public class MultipartSupportService implements IMultipartSupportService { @Override diff --git a/src/test/java/feign/form/MultipartSupportServiceClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java similarity index 89% rename from src/test/java/feign/form/MultipartSupportServiceClient.java rename to feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java index d5904c880..75ae1a566 100644 --- a/src/test/java/feign/form/MultipartSupportServiceClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feign.form; +package feign.form.feign.spring; -import feign.form.FormEncoder; import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; + import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -40,8 +41,8 @@ public class MultipartSupportConfig { @Bean @Primary @Scope("prototype") - public Encoder feignFormEncoder () { - return new FormEncoder(); + public Encoder feignSpringFormEncoder () { + return new SpringFormEncoder(); } } } diff --git a/feign-form/pom.xml b/feign-form/pom.xml new file mode 100644 index 000000000..c781a5fb4 --- /dev/null +++ b/feign-form/pom.xml @@ -0,0 +1,18 @@ + + + + 4.0.0 + jar + + feign-form + + + io.github.openfeign.form + feign-form-parent + 2.0.1 + + + Open Feign Forms Core + \ No newline at end of file diff --git a/src/main/java/feign/form/FormDataProcessor.java b/feign-form/src/main/java/feign/form/FormDataProcessor.java similarity index 95% rename from src/main/java/feign/form/FormDataProcessor.java rename to feign-form/src/main/java/feign/form/FormDataProcessor.java index 5bf810fe8..ea404150f 100644 --- a/src/main/java/feign/form/FormDataProcessor.java +++ b/feign-form/src/main/java/feign/form/FormDataProcessor.java @@ -22,7 +22,7 @@ * @author Artem Labazin * @since 30.04.2016 */ -interface FormDataProcessor { +public interface FormDataProcessor { void process (Map data, RequestTemplate template); diff --git a/src/main/java/feign/form/FormEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java similarity index 96% rename from src/main/java/feign/form/FormEncodedDataProcessor.java rename to feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java index 28448e633..7e397a5c8 100644 --- a/src/main/java/feign/form/FormEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java @@ -27,7 +27,7 @@ * @author Artem Labazin * @since 30.04.2016 */ -class FormEncodedDataProcessor implements FormDataProcessor { +public class FormEncodedDataProcessor implements FormDataProcessor { public static final String CONTENT_TYPE; diff --git a/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java similarity index 66% rename from src/main/java/feign/form/FormEncoder.java rename to feign-form/src/main/java/feign/form/FormEncoder.java index 07d5d7827..a8056b65b 100644 --- a/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -15,15 +15,14 @@ */ package feign.form; -import feign.RequestTemplate; -import feign.codec.Encoder; import java.lang.reflect.Type; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.Map; + +import feign.RequestTemplate; +import feign.codec.Encoder; import lombok.val; -import org.springframework.web.multipart.MultipartFile; /** * @author Artem Labazin @@ -54,7 +53,7 @@ public FormEncoder (Encoder delegate) { @Override public void encode (Object object, Type bodyType, RequestTemplate template) { - if (bodyType != MAP_STRING_WILDCARD && !bodyType.equals(MultipartFile.class)) { + if (bodyType != MAP_STRING_WILDCARD) { deligate.encode(object, bodyType, template); return; } @@ -75,39 +74,13 @@ public void encode (Object object, Type bodyType, RequestTemplate template) { } } - if (formType.isEmpty()) { - formType = detectFormType(object, bodyType); - } - if (formType.isEmpty()) { deligate.encode(object, bodyType, template); return; } - if (object instanceof MultipartFile) { - MultipartFile file = (MultipartFile) object; - Map data = Collections.singletonMap(file.getName(), object); - processors.get(formType).process(data, template); - } else { - @SuppressWarnings("unchecked") - Map data = (Map) object; - processors.get(formType).process(data, template); - } - } - - @SuppressWarnings("unchecked") - private String detectFormType (Object object, Type bodyType) { - if (bodyType == MultipartFile.class) { - return MultipartEncodedDataProcessor.CONTENT_TYPE; - } - if (bodyType == MAP_STRING_WILDCARD) { - for (Object value : ((Map) object).values()) { - if (value instanceof MultipartFile) { - return MultipartEncodedDataProcessor.CONTENT_TYPE; - } - } - } - - return ""; + @SuppressWarnings("unchecked") + Map data = (Map) object; + processors.get(formType).process(data, template); } } diff --git a/src/main/java/feign/form/MultipartEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java similarity index 86% rename from src/main/java/feign/form/MultipartEncodedDataProcessor.java rename to feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java index 9de84693c..d46b9a288 100644 --- a/src/main/java/feign/form/MultipartEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java @@ -17,8 +17,6 @@ import static feign.Util.UTF_8; -import feign.RequestTemplate; -import feign.codec.EncodeException; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -28,15 +26,16 @@ import java.io.PrintWriter; import java.net.URLConnection; import java.util.Map; + +import feign.RequestTemplate; import lombok.SneakyThrows; import lombok.val; -import org.springframework.web.multipart.MultipartFile; /** * @author Artem Labazin * @since 30.04.2016 */ -class MultipartEncodedDataProcessor implements FormDataProcessor { +public class MultipartEncodedDataProcessor implements FormDataProcessor { public static final String CONTENT_TYPE; @@ -94,9 +93,8 @@ private String createBoundary () { return Long.toHexString(System.currentTimeMillis()); } - private boolean isFile (Object value) { - return value != null && - (value instanceof File || value instanceof byte[] || value instanceof MultipartFile); + protected boolean isFile (Object value) { + return value != null && (value instanceof File || value instanceof byte[]); } private void writeParameter (PrintWriter writer, String name, String value) { @@ -105,21 +103,11 @@ private void writeParameter (PrintWriter writer, String name, String value) { writer.append(CRLF).append(value); } - private void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { + protected void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { if (value instanceof byte[]) { writeFile(output, writer, name, (byte[]) value); return; } - - if (value instanceof MultipartFile) { - try { - writeFile(output, writer, name, ((MultipartFile) value).getBytes()); - } catch (IOException e) { - throw new EncodeException("Can't encode MultipartFile", e); - } - return; - } - writeFile(output, writer, name, (File) value); } diff --git a/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java similarity index 96% rename from src/test/java/feign/form/BasicClientTest.java rename to feign-form/src/test/java/feign/form/BasicClientTest.java index b743bacd1..c154e7633 100644 --- a/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -15,13 +15,13 @@ */ package feign.form; -import feign.form.FormEncoder; import feign.Feign; import feign.Headers; import feign.Param; import feign.QueryMap; import feign.RequestLine; import feign.Response; +import feign.form.FormEncoder; import feign.jackson.JacksonEncoder; import java.nio.file.Files; import java.nio.file.Paths; @@ -47,11 +47,7 @@ @RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = DEFINED_PORT, - classes = Server.class, - properties = { - "server.port=8080", - "feign.hystrix.enabled=false" - } + classes = Server.class ) public class BasicClientTest { diff --git a/src/test/java/feign/form/Dto.java b/feign-form/src/test/java/feign/form/Dto.java similarity index 100% rename from src/test/java/feign/form/Dto.java rename to feign-form/src/test/java/feign/form/Dto.java diff --git a/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java similarity index 97% rename from src/test/java/feign/form/Server.java rename to feign-form/src/test/java/feign/form/Server.java index 06ae7d079..2d1c8dca7 100644 --- a/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -26,7 +26,6 @@ import java.util.List; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -43,7 +42,6 @@ */ @Controller @SpringBootApplication -@EnableFeignClients public class Server { @RequestMapping(value = "/form", method = POST) diff --git a/src/test/resources/file.txt b/feign-form/src/test/resources/file.txt similarity index 100% rename from src/test/resources/file.txt rename to feign-form/src/test/resources/file.txt diff --git a/pom.xml b/pom.xml index 6df0416c6..684fb5f37 100644 --- a/pom.xml +++ b/pom.xml @@ -6,9 +6,9 @@ 4.0.0 io.github.openfeign.form - feign-form + feign-form-parent 2.0.1 - jar + pom org.springframework.boot @@ -16,7 +16,7 @@ 1.4.0.RELEASE - Open Feign Forms + Open Feign Forms Parent Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) @@ -163,11 +163,6 @@ 1.16.10 compile - - org.springframework - spring-web - compile - org.springframework.boot spring-boot-starter-web @@ -179,16 +174,15 @@ 8.18.0 test - org.springframework.boot spring-boot-starter-test - - - org.springframework.cloud - spring-cloud-starter-feign test - 1.1.5.RELEASE + + + feign-form + feign-form-spring + \ No newline at end of file From f921edfda9e8b2978ab24f16fee9d5b91e235b23 Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Fri, 16 Sep 2016 17:31:43 +0200 Subject: [PATCH 12/93] Update README.md --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 34ad3f37c..a5b0b8a34 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Then, include dependency to your project: io.github.openfeign.form feign-form - 2.0.0 + 2.0.1 ... @@ -106,6 +106,14 @@ In example above, we send file in parameter named **photo** with additional fiel You can also use Form Encoder with `@FeingClient`: +```xml + + io.github.openfeign.form + feign-form-spring + 2.0.1 + +``` + ```java @FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class) public interface FileUploadServiceClient extends IFileUploadServiceClient { @@ -117,7 +125,7 @@ public interface FileUploadServiceClient extends IFileUploadServiceClient { @Primary @Scope("prototype") public Encoder feignFormEncoder() { - return new FormEncoder(); + return new SpringFormEncoder(); } } } From 409933bd658d79c10aecada80f3c78006827f630 Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Fri, 16 Sep 2016 17:32:15 +0200 Subject: [PATCH 13/93] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a5b0b8a34..49e7836f2 100644 --- a/README.md +++ b/README.md @@ -108,10 +108,10 @@ You can also use Form Encoder with `@FeingClient`: ```xml - io.github.openfeign.form - feign-form-spring - 2.0.1 - + io.github.openfeign.form + feign-form-spring + 2.0.1 + ``` ```java From f7b0616f8ff83b6e07ce03d7b68f350ac08597f7 Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Sun, 18 Sep 2016 08:13:10 +0200 Subject: [PATCH 14/93] Fix groupId in pom.xml. --- feign-form-spring/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index d00a6d6ff..8bd8d306c 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -18,9 +18,9 @@ - ru.xxlabaza + ${project.groupId} feign-form - 2.0.0 + ${project.version} compile From 670dcc1a001a15d70f729547ddeb4e7effb47495 Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Sun, 18 Sep 2016 08:16:50 +0200 Subject: [PATCH 15/93] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 49e7836f2..8ac5a99be 100644 --- a/README.md +++ b/README.md @@ -102,9 +102,9 @@ In example above, we send file in parameter named **photo** with additional fiel > **IMPORTANT:** You can specify your files in API method by declaring type **File** or **byte[]**. -### Spring Cloud Netflix @FeingClient support +### Spring MultipartFile and Spring Cloud Netflix @FeingClient support -You can also use Form Encoder with `@FeingClient`: +You can also use Form Encoder with Spring `MultipartFile` and `@FeingClient`: ```xml From 207a9d6319e473c9e9f18e7fec991749ccd42286 Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Sun, 18 Sep 2016 08:17:21 +0200 Subject: [PATCH 16/93] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ac5a99be..34710f71e 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ In example above, we send file in parameter named **photo** with additional fiel ### Spring MultipartFile and Spring Cloud Netflix @FeingClient support -You can also use Form Encoder with Spring `MultipartFile` and `@FeingClient`: +You can also use Form Encoder with Spring `MultipartFile` and `@FeignClient`: ```xml From 3b9983542c4fa322c781cd573574e854dd2bd9c0 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 18 Sep 2016 20:27:43 +0300 Subject: [PATCH 17/93] - Added javadoc for public and protected methods; - Refactored pom.xml files for 2 spaces tabs; - Added gitignore rules for Netbeans IDE and test's output files. https://github.com/OpenFeign/feign-form/issues/5 --- .gitignore | 13 + feign-form-spring/pom.xml | 57 +-- feign-form/pom.xml | 18 +- .../java/feign/form/FormDataProcessor.java | 13 + .../feign/form/FormEncodedDataProcessor.java | 2 + .../src/main/java/feign/form/FormEncoder.java | 48 ++- .../form/MultipartEncodedDataProcessor.java | 38 +- pom.xml | 341 +++++++++--------- 8 files changed, 307 insertions(+), 223 deletions(-) diff --git a/.gitignore b/.gitignore index 0171a67e7..35386e86e 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,16 @@ buildNumber.properties *.ipr *.iws out/ + +# Netbeans +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +nb-configuration.xml +.nb-gradle/ + +# Test output log file +*.txt diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 8bd8d306c..38b5a189d 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -3,36 +3,37 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - 4.0.0 - jar + 4.0.0 + jar - feign-form-spring + feign-form-spring - - io.github.openfeign.form - feign-form-parent - 2.0.1 - + + io.github.openfeign.form + feign-form-parent + 2.0.1 + - Open Feign Forms Extension for Spring + Open Feign Forms Extension for Spring - - - ${project.groupId} - feign-form - ${project.version} - compile - - - org.springframework - spring-web - compile - - - org.springframework.cloud - spring-cloud-starter-feign - test - 1.1.5.RELEASE - - + + + ${project.groupId} + feign-form + ${project.version} + compile + + + org.springframework + spring-web + compile + + + + org.springframework.cloud + spring-cloud-starter-feign + test + 1.1.5.RELEASE + + \ No newline at end of file diff --git a/feign-form/pom.xml b/feign-form/pom.xml index c781a5fb4..07610dbe6 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -3,16 +3,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - 4.0.0 - jar + 4.0.0 + jar - feign-form + feign-form - - io.github.openfeign.form - feign-form-parent - 2.0.1 - + + io.github.openfeign.form + feign-form-parent + 2.0.1 + - Open Feign Forms Core + Open Feign Forms Core \ No newline at end of file diff --git a/feign-form/src/main/java/feign/form/FormDataProcessor.java b/feign-form/src/main/java/feign/form/FormDataProcessor.java index ea404150f..b0465c4ab 100644 --- a/feign-form/src/main/java/feign/form/FormDataProcessor.java +++ b/feign-form/src/main/java/feign/form/FormDataProcessor.java @@ -19,12 +19,25 @@ import java.util.Map; /** + * Interface for form data processing. + * * @author Artem Labazin * @since 30.04.2016 */ public interface FormDataProcessor { + /** + * Processing form data to request body. + * + * @param data form data, where key is a parameter name and value is...a value. + * @param template current request object. + */ void process (Map data, RequestTemplate template); + /** + * Returns {@code FormDataProcessor} implementation supporting Content-Type. + * + * @return supported MIME Content-Type + */ String getSupportetContentType (); } diff --git a/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java index 7e397a5c8..e54f3eb0e 100644 --- a/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java @@ -24,6 +24,8 @@ import lombok.val; /** + * Form urlencoded implementation of {@link feign.form.FormDataProcessor}. + * * @author Artem Labazin * @since 30.04.2016 */ diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index a8056b65b..193f3165e 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -15,16 +15,48 @@ */ package feign.form; +import feign.RequestTemplate; +import feign.codec.Encoder; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.Map; - -import feign.RequestTemplate; -import feign.codec.Encoder; import lombok.val; /** + * Properly encodes requests with application/x-www-form-urlencoded and multipart/form-data Content-Type. + *

+ * Also, the encoder has a delegate field for encoding non-form requests (like JSON or other). + *

+ * Default delegate object is {@link feign.codec.Encoder.Default} instance. + *

+ * Usage example: + *

+ * Declaring API interface: + *

+ * interface SomeApi {
+ *
+ *     @RequestLine("POST /json")
+ *     @Headers("Content-Type: application/json")
+ *     void json (Dto dto);
+ *
+ *     @RequestLine("POST /form")
+ *     @Headers("Content-Type: application/x-www-form-urlencoded")
+ *     void from (@Param("field1") String field1, @Param("field2") String field2);
+ *
+ * }
+ * 
+ *

+ * Creating Feign client instance: + *

+ * SomeApi api = Feign.builder()
+ *       .encoder(new FormEncoder(new JacksonEncoder()))
+ *       .target(SomeApi.class, "http://localhost:8080");
+ * 
+ *

+ * Now it can handle JSON Content-Type by {@code feign.jackson.JacksonEncoder} and + * form request by {@link feign.form.FormEncoder}. + * * @author Artem Labazin * @since 30.04.2016 */ @@ -34,10 +66,20 @@ public class FormEncoder implements Encoder { private final Map processors; + /** + * Default {@code FormEncoder} constructor. + *

+ * Sets {@link feign.codec.Encoder.Default} instance as delegate encoder. + */ public FormEncoder () { this(new Encoder.Default()); } + /** + * {@code FormEncoder} constructor with delegate encoder argument. + *

+ * @param delegate delegate encoder for processing non-form requests. + */ public FormEncoder (Encoder delegate) { this.deligate = delegate; processors = new HashMap(2, 1.F); diff --git a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java index d46b9a288..e4f645da7 100644 --- a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java @@ -17,6 +17,7 @@ import static feign.Util.UTF_8; +import feign.RequestTemplate; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -26,12 +27,12 @@ import java.io.PrintWriter; import java.net.URLConnection; import java.util.Map; - -import feign.RequestTemplate; import lombok.SneakyThrows; import lombok.val; /** + * Multipart form data implementation of {@link feign.form.FormDataProcessor}. + * * @author Artem Labazin * @since 30.04.2016 */ @@ -89,20 +90,23 @@ public String getSupportetContentType () { return CONTENT_TYPE; } - private String createBoundary () { - return Long.toHexString(System.currentTimeMillis()); - } - + /** + * Checks is passed object a supported file's type or not. + * + * @param value form file parameter. + */ protected boolean isFile (Object value) { return value != null && (value instanceof File || value instanceof byte[]); } - private void writeParameter (PrintWriter writer, String name, String value) { - writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(CRLF); - writer.append("Content-Type: text/plain; charset=UTF-8").append(CRLF); - writer.append(CRLF).append(value); - } - + /** + * Writes file's content to output stream. + * + * @param output output stream to remote destination. + * @param writer wrapped output stream. + * @param name file's name. + * @param value file's content. + */ protected void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { if (value instanceof byte[]) { writeFile(output, writer, name, (byte[]) value); @@ -111,6 +115,16 @@ protected void writeFile (OutputStream output, PrintWriter writer, String name, writeFile(output, writer, name, (File) value); } + private String createBoundary () { + return Long.toHexString(System.currentTimeMillis()); + } + + private void writeParameter (PrintWriter writer, String name, String value) { + writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(CRLF); + writer.append("Content-Type: text/plain; charset=UTF-8").append(CRLF); + writer.append(CRLF).append(value); + } + @SneakyThrows private void writeFile (OutputStream output, PrintWriter writer, String name, File file) { writeFileMeta(writer, name, file.getName()); diff --git a/pom.xml b/pom.xml index 684fb5f37..7b3f8366e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,186 +3,185 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - 4.0.0 + 4.0.0 - io.github.openfeign.form - feign-form-parent - 2.0.1 - pom + io.github.openfeign.form + feign-form-parent + 2.0.1 + pom - - org.springframework.boot - spring-boot-starter-parent - 1.4.0.RELEASE - + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + - Open Feign Forms Parent - - Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) - - https://github.com/OpenFeign/feign-form - 2016 + Open Feign Forms Parent + + Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) + + https://github.com/OpenFeign/feign-form + 2016 - - - Artem Labazin - xxlabaza@gmail.com - - - Tomasz Juchniewicz - tjuchniewicz@gmail.com - - + + + Artem Labazin + xxlabaza@gmail.com + + + Tomasz Juchniewicz + tjuchniewicz@gmail.com + + - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + - - - internal.repo - Temporary Staging Repository - file://${project.build.directory}/mvn-repo - - + + + internal.repo + Temporary Staging Repository + file://${project.build.directory}/mvn-repo + + - - Github - https://github.com/OpenFeign/feign-form/issues - + + Github + https://github.com/OpenFeign/feign-form/issues + - - UTF-8 - 1.6 - 1.6 - 1.8 - 1.8 - github - + + UTF-8 + 1.6 + 1.6 + 1.8 + 1.8 + github + - - - - org.apache.maven.plugins - maven-source-plugin - 2.4 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - -Xdoclint:none - - - - attach-javadocs - - jar - - - - - - maven-deploy-plugin - 2.8.2 - - internal.repo::default::file://${project.build.directory}/mvn-repo - - - - com.github.github - site-maven-plugin - 0.12 - - Maven artifacts for ${project.version} - true - ${project.build.directory}/mvn-repo - refs/heads/mvn-repo - - **/* - - OpenFeign - feign-form - - - - - site - - deploy - - - - - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.15 - - - signature-check - verify - - check - - - - - - org.codehaus.mojo.signature - java16 - 1.0 - - - - - + + feign-form + feign-form-spring + - - - com.netflix.feign - feign-core - 8.18.0 - provided - - - org.projectlombok - lombok - 1.16.10 - compile - - - org.springframework.boot - spring-boot-starter-web - test - - - com.netflix.feign - feign-jackson - 8.18.0 - test - - - org.springframework.boot - spring-boot-starter-test - test - - + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + maven-deploy-plugin + 2.8.2 + + internal.repo::default::file://${project.build.directory}/mvn-repo + + + + com.github.github + site-maven-plugin + 0.12 + + Maven artifacts for ${project.version} + true + ${project.build.directory}/mvn-repo + refs/heads/mvn-repo + + **/* + + OpenFeign + feign-form + + + + + site + + deploy + + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.15 + + + signature-check + verify + + check + + + + + + org.codehaus.mojo.signature + java16 + 1.0 + + + + + - - feign-form - feign-form-spring - + + + io.github.openfeign + feign-core + 9.3.1 + provided + + + org.projectlombok + lombok + compile + + + org.springframework.boot + spring-boot-starter-web + test + + + io.github.openfeign + feign-jackson + 9.3.1 + test + + + org.springframework.boot + spring-boot-starter-test + test + + \ No newline at end of file From 88039a43433e5b076e0c7b3bcd6e804a507f8c22 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 19 Sep 2016 00:09:02 +0300 Subject: [PATCH 18/93] - Corrected pom-files for deploying on bintray; - Updated readme. --- README.md | 39 ++++---- feign-form-spring/pom.xml | 5 +- feign-form/pom.xml | 4 +- pom.xml | 198 +++++++++++++++++++++++--------------- 4 files changed, 141 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 34710f71e..5a5b819af 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,14 @@ This module adds support for encoding **application/x-www-form-urlencoded** and ## Add dependency -Add the following snippet to any project's pom that depends on **feign-form** project: -```xml - - - feign-form - https://raw.github.com/OpenFeign/feign-form/mvn-repo/ - - true - always - - - -``` -Then, include dependency to your project: +Include the dependency to your project's pom.xml file: ```xml ... io.github.openfeign.form feign-form - 2.0.1 + 2.0.2 ... @@ -104,14 +91,24 @@ In example above, we send file in parameter named **photo** with additional fiel ### Spring MultipartFile and Spring Cloud Netflix @FeingClient support -You can also use Form Encoder with Spring `MultipartFile` and `@FeignClient`: +You can also use Form Encoder with Spring `MultipartFile` and `@FeignClient`. +Include the dependencies to your project's pom.xml file: ```xml - - io.github.openfeign.form - feign-form-spring - 2.0.1 - + + ... + + io.github.openfeign.form + feign-form + 2.0.2 + + + io.github.openfeign.form + feign-form-spring + 2.0.2 + + ... + ``` ```java diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 38b5a189d..d4d887820 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -10,8 +10,8 @@ io.github.openfeign.form - feign-form-parent - 2.0.1 + parent + 2.0.2 Open Feign Forms Extension for Spring @@ -20,7 +20,6 @@ ${project.groupId} feign-form - ${project.version} compile diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 07610dbe6..fdba4b79e 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -10,8 +10,8 @@ io.github.openfeign.form - feign-form-parent - 2.0.1 + parent + 2.0.2 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index 7b3f8366e..c8091a736 100644 --- a/pom.xml +++ b/pom.xml @@ -6,8 +6,8 @@ 4.0.0 io.github.openfeign.form - feign-form-parent - 2.0.1 + parent + 2.0.2 pom @@ -16,6 +16,19 @@ 1.4.0.RELEASE + + feign-form + feign-form-spring + + + + UTF-8 + 1.6 + 1.6 + 1.8 + 1.8 + + Open Feign Forms Parent Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) @@ -23,6 +36,21 @@ https://github.com/OpenFeign/feign-form 2016 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + https://github.com/OpenFeign/feign-form + scm:git:https://github.com/OpenFeign/feign-form.git + scm:git:https://github.com/OpenFeign/feign-form.git + 2.0.2 + + Artem Labazin @@ -33,20 +61,11 @@ tjuchniewicz@gmail.com - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - internal.repo - Temporary Staging Repository - file://${project.build.directory}/mvn-repo + bintray + https://api.bintray.com/maven/openfeign/maven/feign-form/;publish=1 @@ -54,20 +73,51 @@ Github https://github.com/OpenFeign/feign-form/issues + + + + + ${project.groupId} + feign-form + ${project.version} + + + ${project.groupId} + feign-form-spring + ${project.version} + + + - - UTF-8 - 1.6 - 1.6 - 1.8 - 1.8 - github - - - - feign-form - feign-form-spring - + + + io.github.openfeign + feign-core + 9.3.1 + provided + + + org.projectlombok + lombok + compile + + + org.springframework.boot + spring-boot-starter-web + test + + + io.github.openfeign + feign-jackson + 9.3.1 + test + + + org.springframework.boot + spring-boot-starter-test + test + + @@ -84,6 +134,7 @@ + org.apache.maven.plugins maven-javadoc-plugin @@ -97,40 +148,29 @@ jar + package + - maven-deploy-plugin - 2.8.2 - - internal.repo::default::file://${project.build.directory}/mvn-repo - - - - com.github.github - site-maven-plugin - 0.12 - - Maven artifacts for ${project.version} - true - ${project.build.directory}/mvn-repo - refs/heads/mvn-repo - - **/* - - OpenFeign - feign-form - + maven-compiler-plugin + + default-compile + compile - site + compile - deploy + + ${main.java.version} + ${main.java.version} + + org.codehaus.mojo animal-sniffer-maven-plugin @@ -152,36 +192,36 @@ + + + maven-install-plugin + 2.5.2 + + true + + + + + maven-release-plugin + 2.5.3 + + false + release + true + @{project.version} + + + + + io.zipkin.centralsync-maven-plugin + centralsync-maven-plugin + 0.1.0 + + openfeign + maven + feign-form + + - - - - io.github.openfeign - feign-core - 9.3.1 - provided - - - org.projectlombok - lombok - compile - - - org.springframework.boot - spring-boot-starter-web - test - - - io.github.openfeign - feign-jackson - 9.3.1 - test - - - org.springframework.boot - spring-boot-starter-test - test - - \ No newline at end of file From 0fe8ac950528e96ef0825a2180ece511a2138111 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 19 Sep 2016 12:19:55 +0300 Subject: [PATCH 19/93] Added travis support --- .settings.xml | 22 +++++++ .travis.yml | 36 ++++++++++++ .travis/publish.sh | 118 ++++++++++++++++++++++++++++++++++++++ feign-form-spring/pom.xml | 20 +++---- feign-form/pom.xml | 18 +++--- pom.xml | 40 ++++++------- 6 files changed, 215 insertions(+), 39 deletions(-) create mode 100644 .settings.xml create mode 100644 .travis.yml create mode 100755 .travis/publish.sh diff --git a/.settings.xml b/.settings.xml new file mode 100644 index 000000000..13da65702 --- /dev/null +++ b/.settings.xml @@ -0,0 +1,22 @@ + + + + sonatype + ${env.SONATYPE_USER} + ${env.SONATYPE_PASSWORD} + + + bintray + ${env.BINTRAY_USER} + ${env.BINTRAY_KEY} + + + github.com + ${env.GH_USER} + ${env.GH_TOKEN} + + + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..615950dea --- /dev/null +++ b/.travis.yml @@ -0,0 +1,36 @@ +# Run `travis lint` when changing this file to avoid breaking the build. +# Default JDK is really old: 1.8.0_31; Trusty's is less old: 1.8.0_51 +# https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments +sudo: required +dist: trusty + +cache: + directories: + - $HOME/.m2 + +language: java + +jdk: + - oraclejdk8 + + +before_install: + # Parameters used during release + - git config user.name "$GH_USER" + - git config user.email "$GH_USER_EMAIL" + # setup https authentication credentials, used by ./mvnw release:prepare + - git config credential.helper "store --file=.git/credentials" + - echo "https://$GH_TOKEN:@github.com" > .git/credentials + +install: + # Override default travis to use the maven wrapper + - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V + +script: + - ./.travis/publish.sh + +# Don't build release tags. This avoids publish conflicts because the version commit exists both on master and the release tag. +# See https://github.com/travis-ci/travis-ci/issues/1532 +branches: + except: + - /^[0-9]/ \ No newline at end of file diff --git a/.travis/publish.sh b/.travis/publish.sh new file mode 100755 index 000000000..d94cdfbec --- /dev/null +++ b/.travis/publish.sh @@ -0,0 +1,118 @@ +# taken from OpenZipkin + +set -euo pipefail +set -x + +build_started_by_tag() { + if [ "${TRAVIS_TAG}" == "" ]; then + echo "[Publishing] This build was not started by a tag, publishing snapshot" + return 1 + else + echo "[Publishing] This build was started by the tag ${TRAVIS_TAG}, publishing release" + return 0 + fi +} + +is_pull_request() { + if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then + echo "[Not Publishing] This is a Pull Request" + return 0 + else + echo "[Publishing] This is not a Pull Request" + return 1 + fi +} + +is_travis_branch_master() { + if [ "${TRAVIS_BRANCH}" = master ]; then + echo "[Publishing] Travis branch is master" + return 0 + else + echo "[Not Publishing] Travis branch is not master" + return 1 + fi +} + +check_travis_branch_equals_travis_tag() { + #Weird comparison comparing branch to tag because when you 'git push --tags' + #the branch somehow becomes the tag value + #github issue: https://github.com/travis-ci/travis-ci/issues/1675 + if [ "${TRAVIS_BRANCH}" != "${TRAVIS_TAG}" ]; then + echo "Travis branch does not equal Travis tag, which it should, bailing out." + echo " github issue: https://github.com/travis-ci/travis-ci/issues/1675" + exit 1 + else + echo "[Publishing] Branch (${TRAVIS_BRANCH}) same as Tag (${TRAVIS_TAG})" + fi +} + +check_release_tag() { + tag="${TRAVIS_TAG}" + if [[ "$tag" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then + echo "Build started by version tag $tag. During the release process tags like this" + echo "are created by the 'release' Maven plugin. Nothing to do here." + exit 0 + elif [[ ! "$tag" =~ ^release-[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then + echo "You must specify a tag of the format 'release-0.0.0' to release this project." + echo "The provided tag ${tag} doesn't match that. Aborting." + exit 1 + fi +} + +is_release_commit() { + project_version=$(mvn help:evaluate -N -Dexpression=project.version|grep -v '\[') + if [[ "$project_version" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then + echo "Build started by release commit $project_version. Will synchronize to maven central." + return 0 + else + return 1 + fi +} + +release_version() { + echo "${TRAVIS_TAG}" | sed 's/^release-//' +} + +safe_checkout_master() { + # We need to be on a branch for release:perform to be able to create commits, and we want that branch to be master. + # But we also want to make sure that we build and release exactly the tagged version, so we verify that the remote + # master is where our tag is. + git checkout -B master + git fetch origin master:origin/master + commit_local_master="$(git show --pretty='format:%H' master)" + commit_remote_master="$(git show --pretty='format:%H' origin/master)" + if [ "$commit_local_master" != "$commit_remote_master" ]; then + echo "Master on remote 'origin' has commits since the version under release, aborting" + exit 1 + fi +} + +#---------------------- +# MAIN +#---------------------- + +if ! is_pull_request && build_started_by_tag; then + check_travis_branch_equals_travis_tag + check_release_tag +fi + +mvn install -nsu + +# If we are on a pull request, our only job is to run tests, which happened above via mvn install +if is_pull_request; then + true +# If we are on master, we will deploy the latest snapshot or release version +# - If a release commit fails to deploy for a transient reason, delete the broken version from bintray and click rebuild +elif is_travis_branch_master; then + mvn --batch-mode -s ./.settings.xml -Prelease -nsu -DskipTests deploy + + # If the deployment succeeded, sync it to Maven Central. Note: this needs to be done once per project, not module, hence -N + if is_release_commit; then + mvn --batch-mode -s ./.settings.xml -nsu -N io.zipkin.centralsync-maven-plugin:centralsync-maven-plugin:sync + fi + +# If we are on a release tag, the following will update any version references and push a version tag for deployment. +elif build_started_by_tag; then + safe_checkout_master + mvn --batch-mode -s ./.settings.xml -Prelease -nsu -DreleaseVersion="$(release_version)" -Darguments="-DskipTests" release:prepare +fi diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index d4d887820..cac41d2fc 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -1,21 +1,21 @@ - - - 4.0.0 + + 4.0.0 jar - + feign-form-spring - + io.github.openfeign.form parent - 2.0.2 + 2.0.3 - - Open Feign Forms Extension for Spring - + + Open Feign Forms Extension for Spring + ${project.groupId} diff --git a/feign-form/pom.xml b/feign-form/pom.xml index fdba4b79e..64df2f5d3 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -1,18 +1,18 @@ - - - 4.0.0 + + 4.0.0 jar - + feign-form - + io.github.openfeign.form parent - 2.0.2 + 2.0.3 - - Open Feign Forms Core + + Open Feign Forms Core \ No newline at end of file diff --git a/pom.xml b/pom.xml index c8091a736..d7e724a33 100644 --- a/pom.xml +++ b/pom.xml @@ -1,26 +1,26 @@ - - + 4.0.0 - + io.github.openfeign.form parent - 2.0.2 + 2.0.3 pom - + org.springframework.boot spring-boot-starter-parent 1.4.0.RELEASE - + feign-form feign-form-spring - + UTF-8 1.6 @@ -28,7 +28,7 @@ 1.8 1.8 - + Open Feign Forms Parent Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) @@ -43,7 +43,7 @@ repo - + https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git @@ -61,7 +61,7 @@ tjuchniewicz@gmail.com - + bintray @@ -73,7 +73,7 @@ Github https://github.com/OpenFeign/feign-form/issues - + @@ -88,7 +88,7 @@ - + io.github.openfeign @@ -118,7 +118,7 @@ test - + @@ -134,7 +134,7 @@ - + org.apache.maven.plugins maven-javadoc-plugin @@ -152,7 +152,7 @@ - + maven-compiler-plugin @@ -170,7 +170,7 @@ - + org.codehaus.mojo animal-sniffer-maven-plugin @@ -192,7 +192,7 @@ - + maven-install-plugin 2.5.2 @@ -200,7 +200,7 @@ true - + maven-release-plugin 2.5.3 @@ -211,7 +211,7 @@ @{project.version} - + io.zipkin.centralsync-maven-plugin centralsync-maven-plugin From 89c9571ebf13a1aa80bc44d60d45e164ed13ea75 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 19 Sep 2016 15:35:19 +0300 Subject: [PATCH 20/93] Added skip tests in travis --- .travis/publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/publish.sh b/.travis/publish.sh index d94cdfbec..a2235b4b6 100755 --- a/.travis/publish.sh +++ b/.travis/publish.sh @@ -96,7 +96,7 @@ if ! is_pull_request && build_started_by_tag; then check_release_tag fi -mvn install -nsu +mvn install -nsu -DskipTests # If we are on a pull request, our only job is to run tests, which happened above via mvn install if is_pull_request; then From 3a7afc25877d783a20cfbd400bda9fa337be3d81 Mon Sep 17 00:00:00 2001 From: Pavel Zhur Date: Thu, 3 Nov 2016 21:56:24 +0300 Subject: [PATCH 21/93] Fix for https://github.com/OpenFeign/feign-form/issues/12 --- .../src/main/java/feign/form/FormEncoder.java | 2 +- .../src/test/java/feign/form/Server.java | 8 +++ .../test/java/feign/form/WildCardMapTest.java | 55 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 feign-form/src/test/java/feign/form/WildCardMapTest.java diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index 193f3165e..ba5423795 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -95,7 +95,7 @@ public FormEncoder (Encoder delegate) { @Override public void encode (Object object, Type bodyType, RequestTemplate template) { - if (bodyType != MAP_STRING_WILDCARD) { + if (!MAP_STRING_WILDCARD.equals(bodyType)) { deligate.encode(object, bodyType, template); return; } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 2d1c8dca7..11a85706d 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -21,6 +21,7 @@ import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT; import static org.springframework.http.HttpStatus.LOCKED; import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -98,4 +99,11 @@ public ResponseEntity queryMap (@RequestParam("filter") List fi } return ResponseEntity.status(status).body(filters.size()); } + + @RequestMapping(value = "/wild-card-map", method = POST, consumes = APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity wildCardMap(@RequestParam("key1") String key1, + @RequestParam("key2") String key2) { + HttpStatus status = key1.equals(key2) ? OK : BAD_REQUEST; + return ResponseEntity.status(status).body(null); + } } diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java new file mode 100644 index 000000000..897388ef4 --- /dev/null +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -0,0 +1,55 @@ +package feign.form; + +import feign.*; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.HashMap; +import java.util.Map; + +import static feign.Logger.Level.FULL; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = DEFINED_PORT, + classes = Server.class +) +public class WildCardMapTest { + + private static FormUrlEncodedApi API; + + @BeforeClass + public static void configureClient() { + API = Feign.builder() + .encoder(new FormEncoder()) + .logger(new Logger.JavaLogger().appendToFile("log.txt")) + .logLevel(FULL) + .target(FormUrlEncodedApi.class, "http://localhost:8080"); + } + + @Test + public void testOk() { + Map param = new HashMap() {{put("key1", "1"); put("key2", "1");}}; + Response response = API.wildCardMap(param); + Assert.assertEquals(200, response.status()); + } + + @Test + public void testBadRequest() { + Map param = new HashMap() {{put("key1", "1"); put("key2", "2");}}; + Response response = API.wildCardMap(param); + Assert.assertEquals(400, response.status()); + } + + interface FormUrlEncodedApi { + + @RequestLine("POST /wild-card-map") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response wildCardMap(Map param); + } +} From ce80cba8bcbedafc60276fbc2f34cb95b2a31043 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 8 Nov 2016 13:27:48 +0300 Subject: [PATCH 22/93] Updated dependencies versions and readme --- .gitignore | 2 +- README.md | 2 +- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- pom.xml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 35386e86e..6891f942b 100644 --- a/.gitignore +++ b/.gitignore @@ -109,4 +109,4 @@ nb-configuration.xml .nb-gradle/ # Test output log file -*.txt +*.txt* diff --git a/README.md b/README.md index 5a5b819af..63a9ef4cd 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.2 + 2.0.4 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index cac41d2fc..2faadd4a3 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.3 + 2.0.4 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 64df2f5d3..c18277751 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.3 + 2.0.4 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index d7e724a33..8187add43 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ io.github.openfeign.form parent - 2.0.3 + 2.0.4 pom org.springframework.boot spring-boot-starter-parent - 1.4.0.RELEASE + 1.4.1.RELEASE From 53805ef72bc051d6c702615ee2f227456a98cd60 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 8 Nov 2016 13:32:52 +0300 Subject: [PATCH 23/93] README update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63a9ef4cd..6823978da 100644 --- a/README.md +++ b/README.md @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.2 + 2.0.4 io.github.openfeign.form feign-form-spring - 2.0.2 + 2.0.4 ... From f4723d1e9bdc6e47fbcb8f42ea2a121203d4ff60 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 8 Nov 2016 13:50:06 +0300 Subject: [PATCH 24/93] Build fix release --- README.md | 6 +++--- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6823978da..d0a2b838d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.4 + 2.0.5 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.4 + 2.0.5 io.github.openfeign.form feign-form-spring - 2.0.4 + 2.0.5 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 2faadd4a3..05e2a748e 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.4 + 2.0.5 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index c18277751..749359172 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.4 + 2.0.5 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index 8187add43..dd2612818 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.github.openfeign.form parent - 2.0.4 + 2.0.5 pom From 09dcd02c3b64cc8db9ca01838540dacd5b186dde Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 18 Nov 2016 00:28:59 +0300 Subject: [PATCH 25/93] Added default content-type. https://github.com/OpenFeign/feign-form/issues/10 --- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- .../feign/form/MultipartEncodedDataProcessor.java | 11 ++++++++--- pom.xml | 8 ++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 05e2a748e..9f448ebee 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.5 + 2.0.6 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 749359172..e697107fd 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.5 + 2.0.6 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java index e4f645da7..e1ef0db47 100644 --- a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java @@ -104,8 +104,8 @@ protected boolean isFile (Object value) { * * @param output output stream to remote destination. * @param writer wrapped output stream. - * @param name file's name. - * @param value file's content. + * @param name file's name. + * @param value file's content. */ protected void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { if (value instanceof byte[]) { @@ -157,9 +157,14 @@ private void writeFileMeta (PrintWriter writer, String name, String fileName) { .append("Content-Disposition: form-data; name=\"").append(name).append("\"; ") .append("filename=\"").append(fileName).append("\"") .toString(); + + String contentValue = URLConnection.guessContentTypeFromName(fileName); + if (contentValue == null) { + contentValue = "application/octet-stream"; + } val contentType = new StringBuilder() .append("Content-Type: ") - .append(URLConnection.guessContentTypeFromName(fileName)) + .append(contentValue) .toString(); writer.append(contentDesposition).append(CRLF); diff --git a/pom.xml b/pom.xml index dd2612818..c61c9e827 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ io.github.openfeign.form parent - 2.0.5 + 2.0.6 pom org.springframework.boot spring-boot-starter-parent - 1.4.1.RELEASE + 1.4.2.RELEASE @@ -48,7 +48,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.0.2 + 2.0.6 @@ -124,7 +124,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.1 attach-sources From c5b2a8382883713557fd4284095a272d1f241c39 Mon Sep 17 00:00:00 2001 From: Vance Thornton Date: Thu, 16 Feb 2017 11:41:25 -0800 Subject: [PATCH 26/93] Modified FormEncoder to ignore the case of the content type header --- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- feign-form/src/main/java/feign/form/FormEncoder.java | 8 ++++---- pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 9f448ebee..b4a6b9248 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.6 + 2.0.7 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index e697107fd..be5abdb64 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.6 + 2.0.7 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index ba5423795..7ec14cd48 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -85,11 +85,11 @@ public FormEncoder (Encoder delegate) { processors = new HashMap(2, 1.F); val formEncodedDataProcessor = new FormEncodedDataProcessor(); - processors.put(formEncodedDataProcessor.getSupportetContentType(), + processors.put(formEncodedDataProcessor.getSupportetContentType().toLowerCase(), formEncodedDataProcessor); val multipartEncodedDataProcessor = new MultipartEncodedDataProcessor(); - processors.put(multipartEncodedDataProcessor.getSupportetContentType(), + processors.put(multipartEncodedDataProcessor.getSupportetContentType().toLowerCase(), multipartEncodedDataProcessor); } @@ -102,11 +102,11 @@ public void encode (Object object, Type bodyType, RequestTemplate template) { String formType = ""; for (Map.Entry> entry : template.headers().entrySet()) { - if (!entry.getKey().equals("Content-Type")) { + if (!entry.getKey().equalsIgnoreCase("Content-Type")) { continue; } for (String contentType : entry.getValue()) { - if (processors.containsKey(contentType)) { + if (contentType != null && processors.containsKey(contentType.toLowerCase())) { formType = contentType; break; } diff --git a/pom.xml b/pom.xml index c61c9e827..46d5a04e1 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.github.openfeign.form parent - 2.0.6 + 2.0.7 pom @@ -48,7 +48,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.0.6 + 2.0.7 From 8118a71a888ab5cbb2e27e3e4c7878c52d349c63 Mon Sep 17 00:00:00 2001 From: Sean Reilly Date: Fri, 17 Feb 2017 11:04:45 +0000 Subject: [PATCH 27/93] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0a2b838d..8eb3b905d 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ In example above, we send file in parameter named **photo** with additional fiel > **IMPORTANT:** You can specify your files in API method by declaring type **File** or **byte[]**. -### Spring MultipartFile and Spring Cloud Netflix @FeingClient support +### Spring MultipartFile and Spring Cloud Netflix @FeignClient support You can also use Form Encoder with Spring `MultipartFile` and `@FeignClient`. From 9ea050c8ab15c9c7d56cf2d5728e216a83f8b987 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 21 Feb 2017 00:18:32 +0300 Subject: [PATCH 28/93] Version correct --- .gitignore | 1 + feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 6891f942b..f3ef4933e 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,4 @@ nb-configuration.xml # Test output log file *.txt* +.DS_Store diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index b4a6b9248..1e94f2795 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.7 + 2.0.8 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index be5abdb64..f5a181894 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.7 + 2.0.8 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index 46d5a04e1..51fb01b21 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.github.openfeign.form parent - 2.0.7 + 2.0.8 pom @@ -48,7 +48,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.0.7 + 2.0.8 From 154ccc1172b04cd95d0cdc76c896d12182b01102 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 21 Feb 2017 00:20:07 +0300 Subject: [PATCH 29/93] Readme update --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8eb3b905d..c79839dd4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.5 + 2.0.8 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.5 + 2.0.8 io.github.openfeign.form feign-form-spring - 2.0.5 + 2.0.8 ... From ce2257f7c55dbc62062c1960af8e89422cfed857 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 21 Feb 2017 00:29:43 +0300 Subject: [PATCH 30/93] Dependencies and copyrights update --- README.md | 6 +++--- feign-form-spring/pom.xml | 2 +- .../spring/SpringMultipartEncodedDataProcessor.java | 6 +++--- .../spring/FeignClientAnnotatedInterfaceTest.java | 2 +- .../form/feign/spring/IMultipartSupportService.java | 4 ++-- .../form/feign/spring/MultipartSupportService.java | 2 +- .../feign/spring/MultipartSupportServiceClient.java | 2 +- feign-form/pom.xml | 2 +- .../src/main/java/feign/form/FormDataProcessor.java | 2 +- .../java/feign/form/FormEncodedDataProcessor.java | 2 +- feign-form/src/main/java/feign/form/FormEncoder.java | 2 +- .../feign/form/MultipartEncodedDataProcessor.java | 2 +- .../src/test/java/feign/form/BasicClientTest.java | 2 +- feign-form/src/test/java/feign/form/Dto.java | 2 +- feign-form/src/test/java/feign/form/Server.java | 2 +- pom.xml | 12 ++++++------ 16 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index c79839dd4..23afb76cf 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.8 + 2.0.9 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.8 + 2.0.9 io.github.openfeign.form feign-form-spring - 2.0.8 + 2.0.9 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 1e94f2795..416ae0067 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.8 + 2.0.9 Open Feign Forms Extension for Spring diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java index d5923d19b..7d6a1047d 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,13 @@ /** * Adds support for {@link MultipartFile} type to {@link MultipartEncodedDataProcessor}. - * + * * @author Tomasz Juchniewicz * @since 14.09.2016 */ public class SpringMultipartEncodedDataProcessor extends MultipartEncodedDataProcessor { - + @Override protected boolean isFile (Object value) { return super.isFile(value) || value instanceof MultipartFile; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java index 7cf666b3c..e6b00c4e4 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java index 79de3a4f7..90fc7ec62 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,5 +50,5 @@ String upload1 (@PathVariable("folder") String folder, String upload2 (@RequestBody MultipartFile file, @PathVariable("folder") String folder, @RequestParam(value = "message", required = false) String message); - + } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java index bcb04d4e1..0aff10d83 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java index 75ae1a566..7fa3185ed 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form/pom.xml b/feign-form/pom.xml index f5a181894..2f9e8354f 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.8 + 2.0.9 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/FormDataProcessor.java b/feign-form/src/main/java/feign/form/FormDataProcessor.java index b0465c4ab..2950df55a 100644 --- a/feign-form/src/main/java/feign/form/FormDataProcessor.java +++ b/feign-form/src/main/java/feign/form/FormDataProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java index e54f3eb0e..8bb765325 100644 --- a/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index 7ec14cd48..eda8a26c0 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java index e1ef0db47..b01af892c 100644 --- a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index c154e7633..af82e1b5f 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form/src/test/java/feign/form/Dto.java b/feign-form/src/test/java/feign/form/Dto.java index aee60a3eb..964926dd7 100644 --- a/feign-form/src/test/java/feign/form/Dto.java +++ b/feign-form/src/test/java/feign/form/Dto.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 11a85706d..2cd78b34e 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Artem Labazin . + * Copyright 2017 Artem Labazin . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/pom.xml b/pom.xml index 51fb01b21..ff8044427 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ io.github.openfeign.form parent - 2.0.8 + 2.0.9 pom org.springframework.boot spring-boot-starter-parent - 1.4.2.RELEASE + 1.4.3.RELEASE @@ -34,7 +34,7 @@ Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) https://github.com/OpenFeign/feign-form - 2016 + 2017 @@ -48,7 +48,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.0.8 + 2.0.9 @@ -93,7 +93,7 @@ io.github.openfeign feign-core - 9.3.1 + 9.4.0 provided @@ -109,7 +109,7 @@ io.github.openfeign feign-jackson - 9.3.1 + 9.4.0 test From 73d3ca457af6c9081014b4183c6f32c25ef909f9 Mon Sep 17 00:00:00 2001 From: Tomasz Juchniewicz Date: Thu, 23 Feb 2017 19:30:28 +0100 Subject: [PATCH 31/93] Extract metadata from MultipartFile Fixes gh-11 --- .../SpringMultipartEncodedDataProcessor.java | 7 +-- .../FeignClientAnnotatedInterfaceTest.java | 8 ++++ .../spring/IMultipartSupportService.java | 10 ++++ .../feign/spring/MultipartSupportService.java | 5 ++ .../form/MultipartEncodedDataProcessor.java | 48 ++++++++++++++----- 5 files changed, 62 insertions(+), 16 deletions(-) diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java index 7d6a1047d..cb5ced4c8 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java @@ -39,16 +39,17 @@ protected boolean isFile (Object value) { } @Override - protected void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { + protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { if (value instanceof MultipartFile) { try { - writeFile(output, writer, name, ((MultipartFile) value).getBytes()); + MultipartFile mpf = (MultipartFile) value; + writeByteArray(output, writer, name, mpf.getOriginalFilename(), mpf.getContentType(), mpf.getBytes()); } catch (IOException e) { throw new EncodeException("Can't encode MultipartFile", e); } return; } - super.writeFile(output, writer, name, value); + super.writeByteOrFile(output, writer, name, value); } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java index e6b00c4e4..39fe6767c 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java @@ -60,4 +60,12 @@ public void upload2Test () throws Exception { Assert.assertEquals("test:message text", response); } + + @Test + public void uploadFileNameAndContentTypeTest () throws Exception { + MultipartFile file = new MockMultipartFile("file", "hello.dat", "application/octet-stream", "test".getBytes(UTF_8)); + String response = client.upload3(file, "test folder", "message text"); + + Assert.assertEquals("hello.dat:application/octet-stream", response); + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java index 90fc7ec62..4feb82eff 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java @@ -51,4 +51,14 @@ String upload2 (@RequestBody MultipartFile file, @PathVariable("folder") String folder, @RequestParam(value = "message", required = false) String message); + @RequestMapping( + value = "/multipart/upload3/{folder}", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseBody + String upload3 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message); + } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java index 0aff10d83..f71c65905 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java @@ -48,4 +48,9 @@ public String upload2(MultipartFile file, String folder, String message) { throw new RuntimeException("Can't get file content", e); } } + + @Override + public String upload3(MultipartFile file, String folder, String message) { + return file.getOriginalFilename() + ":" + file.getContentType(); + } } diff --git a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java index b01af892c..e7b4ba3e2 100644 --- a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java @@ -58,7 +58,7 @@ public void process (Map data, RequestTemplate template) { for (Map.Entry entry : data.entrySet()) { writer.append("--" + boundary).append(CRLF); if (isFile(entry.getValue())) { - writeFile(outputStream, writer, entry.getKey(), entry.getValue()); + writeByteOrFile(outputStream, writer, entry.getKey(), entry.getValue()); } else { writeParameter(writer, entry.getKey(), entry.getValue().toString()); } @@ -104,15 +104,15 @@ protected boolean isFile (Object value) { * * @param output output stream to remote destination. * @param writer wrapped output stream. - * @param name file's name. - * @param value file's content. - */ - protected void writeFile (OutputStream output, PrintWriter writer, String name, Object value) { + * @param name - the name of the file + * @param value - file's content. Byte array or {@link File} + */ + protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { if (value instanceof byte[]) { - writeFile(output, writer, name, (byte[]) value); + writeByteArray(output, writer, name, null, null, (byte[]) value); return; } - writeFile(output, writer, name, (File) value); + writeFile(output, writer, name, null, (File) value); } private String createBoundary () { @@ -125,9 +125,18 @@ private void writeParameter (PrintWriter writer, String name, String value) { writer.append(CRLF).append(value); } + /** + * Writes file to output stream. + * + * @param output output stream to remote destination. + * @param writer wrapped output stream. + * @param name - the name of the file + * @param contentType - the content type (if known) + * @param file - file + */ @SneakyThrows - private void writeFile (OutputStream output, PrintWriter writer, String name, File file) { - writeFileMeta(writer, name, file.getName()); + protected void writeFile (OutputStream output, PrintWriter writer, String name, String contentType, File file) { + writeFileMeta(writer, name, file.getName(), contentType); InputStream input = null; try { @@ -145,20 +154,33 @@ private void writeFile (OutputStream output, PrintWriter writer, String name, Fi writer.flush(); } + /** + * Writes file's content to output stream. + * + * @param output output stream to remote destination. + * @param writer wrapped output stream. + * @param name - the name of the file + * @param originalFilename - the original filename (as on the client's machine) + * @param contentType - the content type (if known) + * @param bytes file's content. + */ @SneakyThrows - private void writeFile (OutputStream output, PrintWriter writer, String name, byte[] bytes) { - writeFileMeta(writer, name, ""); + protected void writeByteArray (OutputStream output, PrintWriter writer, String name, String originalFilename, + String contentType, byte[] bytes) { + writeFileMeta(writer, name, originalFilename, contentType); output.write(bytes); writer.flush(); } - private void writeFileMeta (PrintWriter writer, String name, String fileName) { + private void writeFileMeta (PrintWriter writer, String name, String fileName, String contentValue) { val contentDesposition = new StringBuilder() .append("Content-Disposition: form-data; name=\"").append(name).append("\"; ") .append("filename=\"").append(fileName).append("\"") .toString(); - String contentValue = URLConnection.guessContentTypeFromName(fileName); + if (contentValue == null && fileName != null) { + contentValue = URLConnection.guessContentTypeFromName(fileName); + } if (contentValue == null) { contentValue = "application/octet-stream"; } From c4e84664491858702118985cf884eb5163bc2f21 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 24 Feb 2017 00:07:33 +0300 Subject: [PATCH 32/93] - Replace tabs to spaces; - Code style refactoring; - .gitignore update for VS Code. --- .gitignore | 1 + .../SpringMultipartEncodedDataProcessor.java | 9 ++- .../FeignClientAnnotatedInterfaceTest.java | 10 +-- .../form/MultipartEncodedDataProcessor.java | 66 ++++++++++--------- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index f3ef4933e..e6f45c702 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,4 @@ nb-configuration.xml # Test output log file *.txt* .DS_Store +.vscode/ diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java index cb5ced4c8..002241f5a 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java @@ -32,17 +32,16 @@ */ public class SpringMultipartEncodedDataProcessor extends MultipartEncodedDataProcessor { - @Override - protected boolean isFile (Object value) { - return super.isFile(value) || value instanceof MultipartFile; + protected boolean isPayload (Object value) { + return super.isPayload(value) || value instanceof MultipartFile; } @Override - protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { + protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { if (value instanceof MultipartFile) { try { - MultipartFile mpf = (MultipartFile) value; + MultipartFile mpf = (MultipartFile) value; writeByteArray(output, writer, name, mpf.getOriginalFilename(), mpf.getContentType(), mpf.getBytes()); } catch (IOException e) { throw new EncodeException("Can't encode MultipartFile", e); diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java index 39fe6767c..651e7ce82 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java @@ -60,12 +60,12 @@ public void upload2Test () throws Exception { Assert.assertEquals("test:message text", response); } - + @Test public void uploadFileNameAndContentTypeTest () throws Exception { - MultipartFile file = new MockMultipartFile("file", "hello.dat", "application/octet-stream", "test".getBytes(UTF_8)); - String response = client.upload3(file, "test folder", "message text"); - - Assert.assertEquals("hello.dat:application/octet-stream", response); + MultipartFile file = new MockMultipartFile("file", "hello.dat", "application/octet-stream", "test".getBytes(UTF_8)); + String response = client.upload3(file, "test folder", "message text"); + + Assert.assertEquals("hello.dat:application/octet-stream", response); } } diff --git a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java index e7b4ba3e2..ba069bb90 100644 --- a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java @@ -57,7 +57,7 @@ public void process (Map data, RequestTemplate template) { val writer = new PrintWriter(outputStream); for (Map.Entry entry : data.entrySet()) { writer.append("--" + boundary).append(CRLF); - if (isFile(entry.getValue())) { + if (isPayload(entry.getValue())) { writeByteOrFile(outputStream, writer, entry.getKey(), entry.getValue()); } else { writeParameter(writer, entry.getKey(), entry.getValue().toString()); @@ -95,24 +95,24 @@ public String getSupportetContentType () { * * @param value form file parameter. */ - protected boolean isFile (Object value) { + protected boolean isPayload (Object value) { return value != null && (value instanceof File || value instanceof byte[]); } /** * Writes file's content to output stream. * - * @param output output stream to remote destination. - * @param writer wrapped output stream. - * @param name - the name of the file - * @param value - file's content. Byte array or {@link File} - */ - protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { + * @param output output stream to remote destination. + * @param writer wrapped output stream. + * @param name the name of the file. + * @param value file's content. Byte array or {@link File}. + */ + protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { if (value instanceof byte[]) { writeByteArray(output, writer, name, null, null, (byte[]) value); - return; + } else { + writeFile(output, writer, name, null, (File) value); } - writeFile(output, writer, name, null, (File) value); } private String createBoundary () { @@ -126,14 +126,14 @@ private void writeParameter (PrintWriter writer, String name, String value) { } /** - * Writes file to output stream. + * Writes file to output stream as a {@link File}. * - * @param output output stream to remote destination. - * @param writer wrapped output stream. - * @param name - the name of the file - * @param contentType - the content type (if known) - * @param file - file - */ + * @param output output stream to remote destination. + * @param writer wrapped output stream. + * @param name the name of the file. + * @param contentType the content type (if known). + * @param file file object. + */ @SneakyThrows protected void writeFile (OutputStream output, PrintWriter writer, String name, String contentType, File file) { writeFileMeta(writer, name, file.getName(), contentType); @@ -155,18 +155,23 @@ protected void writeFile (OutputStream output, PrintWriter writer, String name, } /** - * Writes file's content to output stream. + * Writes file's content to output stream as a byte array. * - * @param output output stream to remote destination. - * @param writer wrapped output stream. - * @param name - the name of the file - * @param originalFilename - the original filename (as on the client's machine) - * @param contentType - the content type (if known) - * @param bytes file's content. - */ + * @param output utput stream to remote destination. + * @param writer wrapped output stream. + * @param name the name of the file. + * @param originalFilename the original filename (as on the client's machine). + * @param contentType the content type (if known). + * @param bytes file's content. + */ @SneakyThrows - protected void writeByteArray (OutputStream output, PrintWriter writer, String name, String originalFilename, - String contentType, byte[] bytes) { + protected void writeByteArray (OutputStream output, + PrintWriter writer, + String name, + String originalFilename, + String contentType, + byte[] bytes + ) { writeFileMeta(writer, name, originalFilename, contentType); output.write(bytes); writer.flush(); @@ -178,11 +183,10 @@ private void writeFileMeta (PrintWriter writer, String name, String fileName, St .append("filename=\"").append(fileName).append("\"") .toString(); - if (contentValue == null && fileName != null) { - contentValue = URLConnection.guessContentTypeFromName(fileName); - } if (contentValue == null) { - contentValue = "application/octet-stream"; + contentValue = fileName != null + ? URLConnection.guessContentTypeFromName(fileName) + : "application/octet-stream"; } val contentType = new StringBuilder() .append("Content-Type: ") From 5f55216c068020e959bc165210797b7efca845ca Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 24 Feb 2017 00:12:55 +0300 Subject: [PATCH 33/93] Update to new version --- README.md | 6 +++--- feign-form-spring/pom.xml | 4 ++-- feign-form/pom.xml | 4 ++-- pom.xml | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 23afb76cf..e01449547 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.9 + 2.1.0 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.0.9 + 2.1.0 io.github.openfeign.form feign-form-spring - 2.0.9 + 2.1.0 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 416ae0067..0e3879de1 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.0.9 + 2.1.0 Open Feign Forms Extension for Spring @@ -35,4 +35,4 @@ 1.1.5.RELEASE - \ No newline at end of file + diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 2f9e8354f..51b6c597d 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,8 +11,8 @@ io.github.openfeign.form parent - 2.0.9 + 2.1.0 Open Feign Forms Core - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index ff8044427..382836996 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.github.openfeign.form parent - 2.0.9 + 2.1.0 pom @@ -48,7 +48,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.0.9 + 2.1.0 @@ -224,4 +224,4 @@ - \ No newline at end of file + From c0af65175c1b255782a2ae5c9be299cd6f3bf3f4 Mon Sep 17 00:00:00 2001 From: bhathiya Date: Tue, 6 Jun 2017 15:24:59 +0530 Subject: [PATCH 34/93] OSGified feign-form jars --- feign-form-spring/pom.xml | 24 ++++++++++++++++++++++++ feign-form/pom.xml | 24 ++++++++++++++++++++++++ pom.xml | 16 ++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 0e3879de1..a25514e0f 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -35,4 +35,28 @@ 1.1.5.RELEASE + + + + + org.apache.felix + maven-bundle-plugin + ${maven-bundle-plugin.version} + + + bundle-manifest + process-classes + + manifest + + + + + + feign.form.spring + + + + + diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 51b6c597d..84ae00088 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -15,4 +15,28 @@ Open Feign Forms Core + + + + + org.apache.felix + maven-bundle-plugin + ${maven-bundle-plugin.version} + + + bundle-manifest + process-classes + + manifest + + + + + + feign.form + + + + + diff --git a/pom.xml b/pom.xml index 382836996..9d51a92b0 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,8 @@ 1.6 1.8 1.8 + 3.2.0 + 2.6 Open Feign Forms Parent @@ -120,6 +122,20 @@ + + + + maven-jar-plugin + ${maven-jar-plugin.version} + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins From a88613ba5a91bfe96d77bc57b2bb17a5153e6233 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 9 Jun 2017 00:35:36 +0300 Subject: [PATCH 35/93] Update dependencies and increment project version --- README.md | 6 +++--- feign-form-spring/pom.xml | 4 ++-- feign-form/pom.xml | 2 +- pom.xml | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e01449547..8101af6cb 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.1.0 + 2.2.0 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.1.0 + 2.2.0 io.github.openfeign.form feign-form-spring - 2.1.0 + 2.2.0 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index a25514e0f..54274c76e 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.1.0 + 2.2.0 Open Feign Forms Extension for Spring @@ -32,7 +32,7 @@ org.springframework.cloud spring-cloud-starter-feign test - 1.1.5.RELEASE + 1.3.1.RELEASE diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 84ae00088..3b13adb60 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.1.0 + 2.2.0 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index 9d51a92b0..8baab9708 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ io.github.openfeign.form parent - 2.1.0 + 2.2.0 pom org.springframework.boot spring-boot-starter-parent - 1.4.3.RELEASE + 1.5.3.RELEASE @@ -50,7 +50,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.1.0 + 2.2.0 @@ -95,7 +95,7 @@ io.github.openfeign feign-core - 9.4.0 + 9.5.0 provided @@ -111,7 +111,7 @@ io.github.openfeign feign-jackson - 9.4.0 + 9.5.0 test From 261b985049c97dc3cc0bba8df0991ba645a15c78 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sat, 5 Aug 2017 13:29:10 +0300 Subject: [PATCH 36/93] Lombok is now provided. Update dependencies --- README.md | 6 +++--- feign-form-spring/pom.xml | 4 ++-- feign-form/pom.xml | 2 +- pom.xml | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8101af6cb..2e72afc51 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.2.0 + 2.2.1 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.2.0 + 2.2.1 io.github.openfeign.form feign-form-spring - 2.2.0 + 2.2.1 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 54274c76e..001b985c1 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.2.0 + 2.2.1 Open Feign Forms Extension for Spring @@ -32,7 +32,7 @@ org.springframework.cloud spring-cloud-starter-feign test - 1.3.1.RELEASE + 1.3.2.RELEASE diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 3b13adb60..b0ad318aa 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -11,7 +11,7 @@ io.github.openfeign.form parent - 2.2.0 + 2.2.1 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index 8baab9708..8be841c1b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.github.openfeign.form parent - 2.2.0 + 2.2.1 pom @@ -50,7 +50,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.2.0 + 2.2.1 @@ -95,13 +95,13 @@ io.github.openfeign feign-core - 9.5.0 + 9.5.1 provided org.projectlombok lombok - compile + provided org.springframework.boot @@ -111,7 +111,7 @@ io.github.openfeign feign-jackson - 9.5.0 + 9.5.1 test From 6ee0876ea71d3559eb93279eb943caee8dca3850 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 24 Nov 2017 19:30:15 +0300 Subject: [PATCH 37/93] Refactoring --- README.md | 28 ++- feign-form-spring/pom.xml | 20 +- .../feign/form/spring/SpringFormEncoder.java | 76 ++++--- .../SpringManyMultipartFilesWriter.java | 68 ++++++ .../SpringMultipartEncodedDataProcessor.java | 54 ----- .../SpringSingleMultipartFileWriter.java | 43 ++++ .../java/feign/form/feign/spring/Client.java | 96 +++++++++ .../FeignClientAnnotatedInterfaceTest.java | 71 ------- .../spring/IMultipartSupportService.java | 64 ------ .../feign/spring/MultipartSupportService.java | 56 ----- .../spring/MultipartSupportServiceClient.java | 48 ----- .../java/feign/form/feign/spring/Server.java | 91 ++++++++ .../feign/spring/SpringFormEncoderTest.java | 101 +++++++++ feign-form/pom.xml | 22 +- .../java/feign/form/ContentProcessor.java | 54 +++++ .../src/main/java/feign/form/ContentType.java | 60 ++++++ .../java/feign/form/FormDataProcessor.java | 43 ---- .../feign/form/FormEncodedDataProcessor.java | 67 ------ .../src/main/java/feign/form/FormEncoder.java | 179 ++++++++-------- .../form/MultipartEncodedDataProcessor.java | 201 ------------------ .../form/MultipartFormContentProcessor.java | 110 ++++++++++ .../form/UrlencodedFormContentProcessor.java | 66 ++++++ .../feign/form/multipart/AbstractWriter.java | 78 +++++++ .../feign/form/multipart/ByteArrayWriter.java | 37 ++++ .../feign/form/multipart/DelegateWriter.java | 51 +++++ .../feign/form/multipart/ManyFilesWriter.java | 66 ++++++ .../java/feign/form/multipart/Output.java | 67 ++++++ .../feign/form/multipart/ParameterWriter.java | 49 +++++ .../form/multipart/SingleFileWriter.java | 54 +++++ .../java/feign/form/multipart/Writer.java | 43 ++++ .../test/java/feign/form/BasicClientTest.java | 193 ++++++++++------- feign-form/src/test/java/feign/form/Dto.java | 14 +- .../src/test/java/feign/form/Server.java | 160 +++++++++----- .../src/test/java/feign/form/TestClient.java | 68 ++++++ .../test/java/feign/form/WildCardMapTest.java | 82 ++++--- pom.xml | 29 ++- 36 files changed, 1706 insertions(+), 903 deletions(-) create mode 100644 feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java delete mode 100644 feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java create mode 100644 feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/Client.java delete mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java delete mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java delete mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java delete mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/Server.java create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java create mode 100644 feign-form/src/main/java/feign/form/ContentProcessor.java create mode 100644 feign-form/src/main/java/feign/form/ContentType.java delete mode 100644 feign-form/src/main/java/feign/form/FormDataProcessor.java delete mode 100644 feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java delete mode 100644 feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java create mode 100644 feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java create mode 100644 feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java create mode 100644 feign-form/src/main/java/feign/form/multipart/AbstractWriter.java create mode 100644 feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java create mode 100644 feign-form/src/main/java/feign/form/multipart/DelegateWriter.java create mode 100644 feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java create mode 100644 feign-form/src/main/java/feign/form/multipart/Output.java create mode 100644 feign-form/src/main/java/feign/form/multipart/ParameterWriter.java create mode 100644 feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java create mode 100644 feign-form/src/main/java/feign/form/multipart/Writer.java create mode 100644 feign-form/src/test/java/feign/form/TestClient.java diff --git a/README.md b/README.md index 2e72afc51..6b9175ad3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 2.2.1 + 3.0.0 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 2.2.1 + 3.0.0 io.github.openfeign.form feign-form-spring - 2.2.1 + 3.0.0 ... @@ -115,12 +115,28 @@ Include the dependencies to your project's pom.xml file: @FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class) public interface FileUploadServiceClient extends IFileUploadServiceClient { - @Configuration + public class MultipartSupportConfig { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public Encoder feignFormEncoder() { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } + } +} +``` + +Or, if you don't need Spring's standard encoder: + +```java +@FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class) +public interface FileUploadServiceClient extends IFileUploadServiceClient { + public class MultipartSupportConfig { @Bean - @Primary - @Scope("prototype") public Encoder feignFormEncoder() { return new SpringFormEncoder(); } diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 001b985c1..729001b6f 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -1,4 +1,20 @@ + + @@ -11,7 +27,7 @@ io.github.openfeign.form parent - 2.2.1 + 3.0.0 Open Feign Forms Extension for Spring @@ -31,8 +47,8 @@ org.springframework.cloud spring-cloud-starter-feign + 1.3.5.RELEASE test - 1.3.2.RELEASE diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java index 3ccbe29c8..0c198be20 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java @@ -1,44 +1,70 @@ -package feign.form.spring; +/* + * Copyright 2017 Artem Labazin . + * + * 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. + */ -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.Map; +package feign.form.spring; -import org.springframework.web.multipart.MultipartFile; +import static feign.form.ContentType.MULTIPART; +import static java.util.Collections.singletonMap; import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; import feign.form.FormEncoder; +import feign.form.MultipartFormContentProcessor; +import java.lang.reflect.Type; +import lombok.val; +import org.springframework.web.multipart.MultipartFile; /** * Adds support for {@link MultipartFile} type to {@link FormEncoder}. - * + * * @author Tomasz Juchniewicz * @since 14.09.2016 */ public class SpringFormEncoder extends FormEncoder { - - private final Encoder delegate; - public SpringFormEncoder () { - this(new Encoder.Default()); - } + /** + * Constructor with the default Feign's encoder as a delegate. + */ + public SpringFormEncoder () { + this(new Encoder.Default()); + } - public SpringFormEncoder(Encoder delegate) { - this.delegate = delegate; - } - - @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - if (!bodyType.equals(MultipartFile.class)) { - delegate.encode(object, bodyType, template); - return; - } - - MultipartFile file = (MultipartFile) object; - Map data = Collections.singletonMap(file.getName(), object); - new SpringMultipartEncodedDataProcessor().process(data, template); + /** + * Constructor with specified delegate encoder. + * + * @param delegate delegate encoder, if this encoder couldn't encode object. + */ + public SpringFormEncoder (Encoder delegate) { + super(delegate); + + val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART); + processor.addWriter(new SpringSingleMultipartFileWriter()); + processor.addWriter(new SpringManyMultipartFilesWriter()); + } + + @Override + public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException { + if (!bodyType.equals(MultipartFile.class)) { + super.encode(object, bodyType, template); + return; } + val file = (MultipartFile) object; + val data = singletonMap(file.getName(), object); + super.encode(data, MAP_STRING_WILDCARD, template); + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java new file mode 100644 index 000000000..edca9f304 --- /dev/null +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.spring; + +import static lombok.AccessLevel.PRIVATE; + +import feign.form.multipart.AbstractWriter; +import feign.form.multipart.Output; +import lombok.experimental.FieldDefaults; +import lombok.val; +import org.springframework.web.multipart.MultipartFile; + +/** + * + * @author Artem Labazin + */ +@FieldDefaults(level = PRIVATE, makeFinal = true) +public class SpringManyMultipartFilesWriter extends AbstractWriter { + + SpringSingleMultipartFileWriter fileWriter = new SpringSingleMultipartFileWriter(); + + @Override + public void write (Output output, String boundary, String key, Object value) throws Exception { + if (value instanceof MultipartFile[]) { + val files = (MultipartFile[]) value; + for (val file : files) { + fileWriter.write(output, boundary, key, file); + } + } else if (value instanceof Iterable) { + val iterable = (Iterable) value; + for (val file : iterable) { + fileWriter.write(output, boundary, key, file); + } + } + } + + @Override + public boolean isApplicable (Object value) { + if (value == null) { + return false; + } + if (value instanceof MultipartFile[]) { + return true; + } + if (value instanceof Iterable) { + val iterable = (Iterable) value; + val iterator = iterable.iterator(); + if (iterator.hasNext() && iterator.next() instanceof MultipartFile) { + return true; + } + } + return false; + } +} diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java b/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java deleted file mode 100644 index 002241f5a..000000000 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringMultipartEncodedDataProcessor.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form.spring; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; - -import org.springframework.web.multipart.MultipartFile; - -import feign.codec.EncodeException; -import feign.form.MultipartEncodedDataProcessor; - -/** - * Adds support for {@link MultipartFile} type to {@link MultipartEncodedDataProcessor}. - * - * @author Tomasz Juchniewicz - * @since 14.09.2016 - */ -public class SpringMultipartEncodedDataProcessor extends MultipartEncodedDataProcessor { - - @Override - protected boolean isPayload (Object value) { - return super.isPayload(value) || value instanceof MultipartFile; - } - - @Override - protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { - if (value instanceof MultipartFile) { - try { - MultipartFile mpf = (MultipartFile) value; - writeByteArray(output, writer, name, mpf.getOriginalFilename(), mpf.getContentType(), mpf.getBytes()); - } catch (IOException e) { - throw new EncodeException("Can't encode MultipartFile", e); - } - return; - } - - super.writeByteOrFile(output, writer, name, value); - } -} diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java new file mode 100644 index 000000000..b31a5bb4c --- /dev/null +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.spring; + +import feign.form.multipart.AbstractWriter; +import feign.form.multipart.Output; +import lombok.val; +import org.springframework.web.multipart.MultipartFile; + +/** + * + * @author Artem Labazin + */ +public class SpringSingleMultipartFileWriter extends AbstractWriter { + + @Override + public boolean isApplicable (Object value) { + return value != null && value instanceof MultipartFile; + } + + @Override + protected void write (Output output, String key, Object value) throws Exception { + val file = (MultipartFile) value; + + writeFileMetadata(output, key, file.getOriginalFilename(), file.getContentType()); + + output.write(file.getBytes()); + } +} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java new file mode 100644 index 000000000..e42cb0d18 --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -0,0 +1,96 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.feign.spring; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; +import java.util.Map; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.HttpMessageConverters; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.netflix.feign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +/** + * + * @author Artem Labazin + */ +@FeignClient( + name = "multipart-support-service", + url = "http://localhost:8080", + configuration = Client.ClientConfiguration.class +) +public interface Client { + + @RequestMapping( + value = "/multipart/upload1/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + String upload1 (@PathVariable("folder") String folder, + @RequestPart MultipartFile file, + @RequestParam(value = "message", required = false) String message); + + @RequestMapping( + value = "/multipart/upload2/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + String upload2 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message); + + @RequestMapping( + value = "/multipart/upload3/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + String upload3 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message); + + @RequestMapping( + path = "/multipart/upload4/{id}", + method = POST, + produces = APPLICATION_JSON_VALUE + ) + String upload4 (@PathVariable("id") String id, + @RequestBody Map map, + @RequestParam("userName") String userName); + + public static class ClientConfiguration { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public Encoder feignEncoder () { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } + } +} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java deleted file mode 100644 index 651e7ce82..000000000 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/FeignClientAnnotatedInterfaceTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form.feign.spring; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.multipart.MultipartFile; - -/** - * @author Tomasz Juchniewicz - * @since 22.08.2016 - */ -@RunWith(SpringRunner.class) -@SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = MultipartSupportService.class, - properties = { - "server.port=8080", - "feign.hystrix.enabled=false" - } -) -public class FeignClientAnnotatedInterfaceTest { - - @Autowired - private MultipartSupportServiceClient client; - - @Test - public void upload1Test () throws Exception { - MultipartFile file = new MockMultipartFile("file", "test".getBytes(UTF_8)); - String response = client.upload1("test folder", file, "message text"); - - Assert.assertEquals("test:message text", response); - } - - @Test - public void upload2Test () throws Exception { - MultipartFile file = new MockMultipartFile("file", "test".getBytes(UTF_8)); - String response = client.upload2(file, "test folder", "message text"); - - Assert.assertEquals("test:message text", response); - } - - @Test - public void uploadFileNameAndContentTypeTest () throws Exception { - MultipartFile file = new MockMultipartFile("file", "hello.dat", "application/octet-stream", "test".getBytes(UTF_8)); - String response = client.upload3(file, "test folder", "message text"); - - Assert.assertEquals("hello.dat:application/octet-stream", response); - } -} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java deleted file mode 100644 index 4feb82eff..000000000 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/IMultipartSupportService.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form.feign.spring; - -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -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.RequestPart; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.multipart.MultipartFile; - -/** - * @author Tomasz Juchniewicz - * @since 22.08.2016 - */ -public interface IMultipartSupportService { - - @RequestMapping( - value = "/multipart/upload1/{folder}", - method = RequestMethod.POST, - produces = MediaType.APPLICATION_JSON_VALUE - ) - @ResponseBody - String upload1 (@PathVariable("folder") String folder, - @RequestPart MultipartFile file, - @RequestParam(value = "message", required = false) String message); - - @RequestMapping( - value = "/multipart/upload2/{folder}", - method = RequestMethod.POST, - produces = MediaType.APPLICATION_JSON_VALUE - ) - @ResponseBody - String upload2 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message); - - @RequestMapping( - value = "/multipart/upload3/{folder}", - method = RequestMethod.POST, - produces = MediaType.APPLICATION_JSON_VALUE - ) - @ResponseBody - String upload3 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message); - -} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java deleted file mode 100644 index f71c65905..000000000 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportService.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form.feign.spring; - -import java.io.IOException; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.feign.EnableFeignClients; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -/** - * @author Tomasz Juchniewicz - * @since 22.08.2016 - */ -@SpringBootApplication -@RestController -@EnableFeignClients -public class MultipartSupportService implements IMultipartSupportService { - - @Override - public String upload1(String folder, MultipartFile file, String message) { - try { - return new String(file.getBytes()) + ":" + message; - } catch (IOException e) { - throw new RuntimeException("Can't get file content", e); - } - } - - @Override - public String upload2(MultipartFile file, String folder, String message) { - try { - return new String(file.getBytes()) + ":" + message; - } catch (IOException e) { - throw new RuntimeException("Can't get file content", e); - } - } - - @Override - public String upload3(MultipartFile file, String folder, String message) { - return file.getOriginalFilename() + ":" + file.getContentType(); - } -} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java deleted file mode 100644 index 7fa3185ed..000000000 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/MultipartSupportServiceClient.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form.feign.spring; - -import feign.codec.Encoder; -import feign.form.spring.SpringFormEncoder; - -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.context.annotation.Scope; - -/** - * @author Tomasz Juchniewicz - * @since 22.08.2016 - */ -@FeignClient( - name = "multipart-support-service", - url = "http://localhost:8080", - configuration = MultipartSupportServiceClient.MultipartSupportConfig.class -) -public interface MultipartSupportServiceClient extends IMultipartSupportService { - - @Configuration - public class MultipartSupportConfig { - - @Bean - @Primary - @Scope("prototype") - public Encoder feignSpringFormEncoder () { - return new SpringFormEncoder(); - } - } -} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java new file mode 100644 index 000000000..81bbd5a84 --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.feign.spring; + +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +import java.util.Map; +import lombok.SneakyThrows; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * @author Tomasz Juchniewicz + * @since 22.08.2016 + */ +@RestController +@EnableFeignClients +@SpringBootApplication +public class Server { + + @RequestMapping( + value = "/multipart/upload1/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + @SneakyThrows + public String upload1 (@PathVariable("folder") String folder, + @RequestPart MultipartFile file, + @RequestParam(value = "message", required = false) String message + ) { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } + + @RequestMapping( + value = "/multipart/upload2/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + @SneakyThrows + public String upload2 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message + ) { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } + + @RequestMapping( + value = "/multipart/upload3/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + public String upload3 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message + ) { + return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; + } + + @RequestMapping( + path = "/multipart/upload4/{id}", + method = POST + ) + public String upload4 (@PathVariable("id") String id, + @RequestBody Map map, + @RequestParam String userName + ) { + return userName + ':' + id + ':' + map.size(); + } +} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java new file mode 100644 index 000000000..277bab7f2 --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.feign.spring; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +import java.util.HashMap; +import lombok.val; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Tomasz Juchniewicz + * @since 22.08.2016 + */ +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = DEFINED_PORT, + classes = Server.class, + properties = { + "server.port=8080", + "feign.hystrix.enabled=false" + } +) +public class SpringFormEncoderTest { + + @Autowired + private Client client; + + @Test + public void upload1Test () throws Exception { + val folder = "test_folder"; + val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); + val message = "message test"; + + val response = client.upload1(folder, file, message); + + Assert.assertEquals(new String(file.getBytes()) + ':' + message + ':' + folder, response); + } + + @Test + public void upload2Test () throws Exception { + val folder = "test_folder"; + val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); + val message = "message test"; + + String response = client.upload2(file, folder, message); + + Assert.assertEquals(new String(file.getBytes()) + ':' + message + ':' + folder, response); + } + + @Test + public void uploadFileNameAndContentTypeTest () throws Exception { + val folder = "test_folder"; + val file = new MockMultipartFile( + "file", + "hello.dat", + "application/octet-stream", + "test".getBytes(UTF_8) + ); + val message = "message test"; + + val response = client.upload3(file, folder, message); + + Assert.assertEquals(file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder, response); + } + + @Test + public void upload4Test () throws Exception { + val map = new HashMap<>(); + map.put("one", 1); + map.put("two", 2); + + val userName = "popa"; + val id = "42"; + + val response = client.upload4(id, map, userName); + + Assert.assertEquals(userName + ':' + id + ':' + map.size(), response); + } +} diff --git a/feign-form/pom.xml b/feign-form/pom.xml index b0ad318aa..888d5bae4 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -1,4 +1,20 @@ + + @@ -11,7 +27,7 @@ io.github.openfeign.form parent - 2.2.1 + 3.0.0 Open Feign Forms Core @@ -39,4 +55,8 @@ + + 1.6 + 1.6 + diff --git a/feign-form/src/main/java/feign/form/ContentProcessor.java b/feign-form/src/main/java/feign/form/ContentProcessor.java new file mode 100644 index 000000000..977325115 --- /dev/null +++ b/feign-form/src/main/java/feign/form/ContentProcessor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form; + +import feign.RequestTemplate; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * Interface for content processors. + * + * @see MultipartFormContentProcessor + * @see UrlencodedFormContentProcessor + * + * @author Artem Labazin + */ +public interface ContentProcessor { + + String CONTENT_TYPE_HEADER = "Content-Type"; + + String CRLF = "\r\n"; + + /** + * Processes a request. + * + * @param template Feign's request template. + * @param charset request charset from 'Content-Type' header (UTF-8 by default). + * @param data reqeust data. + * + * @throws Exception in case of...exception + */ + void process (RequestTemplate template, Charset charset, Map data) throws Exception; + + /** + * Returns supported {@link ContentType} of this processor. + * + * @return supported content type enum value. + */ + ContentType getSupportedContentType (); +} diff --git a/feign-form/src/main/java/feign/form/ContentType.java b/feign-form/src/main/java/feign/form/ContentType.java new file mode 100644 index 000000000..981d30df8 --- /dev/null +++ b/feign-form/src/main/java/feign/form/ContentType.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form; + +import lombok.Getter; +import lombok.val; + +/** + * Supported content types. + * + * @author Artem Labazin + */ +public enum ContentType { + + UNDEFINED("undefined"), + URLENCODED("application/x-www-form-urlencoded"), + MULTIPART("multipart/form-data"); + + @Getter + private final String header; + + private ContentType (String header) { + this.header = header; + } + + /** + * Parses string to content type. + * + * @param str string representation of content type. + * + * @return {@link ContentType} instance or {@link ContentType#UNDEFINED}, if there is no such content type. + */ + public static ContentType of (String str) { + if (str == null) { + return UNDEFINED; + } + + val trimmed = str.trim(); + for (val type : values()) { + if (trimmed.startsWith(type.getHeader())) { + return type; + } + } + return UNDEFINED; + } +} diff --git a/feign-form/src/main/java/feign/form/FormDataProcessor.java b/feign-form/src/main/java/feign/form/FormDataProcessor.java deleted file mode 100644 index 2950df55a..000000000 --- a/feign-form/src/main/java/feign/form/FormDataProcessor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form; - -import feign.RequestTemplate; -import java.util.Map; - -/** - * Interface for form data processing. - * - * @author Artem Labazin - * @since 30.04.2016 - */ -public interface FormDataProcessor { - - /** - * Processing form data to request body. - * - * @param data form data, where key is a parameter name and value is...a value. - * @param template current request object. - */ - void process (Map data, RequestTemplate template); - - /** - * Returns {@code FormDataProcessor} implementation supporting Content-Type. - * - * @return supported MIME Content-Type - */ - String getSupportetContentType (); -} diff --git a/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java deleted file mode 100644 index 8bb765325..000000000 --- a/feign-form/src/main/java/feign/form/FormEncodedDataProcessor.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form; - -import static feign.Util.UTF_8; - -import feign.RequestTemplate; -import java.net.URLEncoder; -import java.util.Map; -import lombok.SneakyThrows; -import lombok.val; - -/** - * Form urlencoded implementation of {@link feign.form.FormDataProcessor}. - * - * @author Artem Labazin - * @since 30.04.2016 - */ -public class FormEncodedDataProcessor implements FormDataProcessor { - - public static final String CONTENT_TYPE; - - static { - CONTENT_TYPE = "application/x-www-form-urlencoded"; - } - - @Override - public void process (Map data, RequestTemplate template) { - val body = new StringBuilder(); - for (Map.Entry entry : data.entrySet()) { - if (body.length() > 0) { - body.append('&'); - } - body.append(createKeyValuePair(entry)); - } - - template.header("Content-Type", CONTENT_TYPE); - template.body(body.toString()); - } - - @Override - public String getSupportetContentType () { - return CONTENT_TYPE; - } - - @SneakyThrows - private String createKeyValuePair (Map.Entry entry) { - return new StringBuilder() - .append(URLEncoder.encode(entry.getKey(), UTF_8.name())) - .append('=') - .append(URLEncoder.encode(entry.getValue().toString(), UTF_8.name())) - .toString(); - } -} diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index eda8a26c0..c4b651bb1 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -13,116 +13,121 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package feign.form; +import static java.util.Arrays.asList; +import static lombok.AccessLevel.PRIVATE; + import feign.RequestTemplate; +import feign.codec.EncodeException; import feign.codec.Encoder; import java.lang.reflect.Type; +import java.nio.charset.Charset; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.regex.Pattern; +import lombok.SneakyThrows; +import lombok.experimental.FieldDefaults; import lombok.val; /** - * Properly encodes requests with application/x-www-form-urlencoded and multipart/form-data Content-Type. - *

- * Also, the encoder has a delegate field for encoding non-form requests (like JSON or other). - *

- * Default delegate object is {@link feign.codec.Encoder.Default} instance. - *

- * Usage example: - *

- * Declaring API interface: - *

- * interface SomeApi {
- *
- *     @RequestLine("POST /json")
- *     @Headers("Content-Type: application/json")
- *     void json (Dto dto);
- *
- *     @RequestLine("POST /form")
- *     @Headers("Content-Type: application/x-www-form-urlencoded")
- *     void from (@Param("field1") String field1, @Param("field2") String field2);
- *
- * }
- * 
- *

- * Creating Feign client instance: - *

- * SomeApi api = Feign.builder()
- *       .encoder(new FormEncoder(new JacksonEncoder()))
- *       .target(SomeApi.class, "http://localhost:8080");
- * 
- *

- * Now it can handle JSON Content-Type by {@code feign.jackson.JacksonEncoder} and - * form request by {@link feign.form.FormEncoder}. * * @author Artem Labazin - * @since 30.04.2016 */ +@FieldDefaults(level = PRIVATE, makeFinal = true) public class FormEncoder implements Encoder { - private final Encoder deligate; + private static final String CONTENT_TYPE_HEADER; + + private static final Pattern CHARSET_PATTERN; + + private static final Charset DEFAULT_CHARSET; + + static { + CONTENT_TYPE_HEADER = "Content-Type"; + CHARSET_PATTERN = Pattern.compile("(?<=charset=)([\\w\\-]+)"); + DEFAULT_CHARSET = Charset.forName("UTF-8"); + } + + Encoder delegate; - private final Map processors; + Map processors; - /** - * Default {@code FormEncoder} constructor. - *

- * Sets {@link feign.codec.Encoder.Default} instance as delegate encoder. - */ - public FormEncoder () { - this(new Encoder.Default()); + /** + * Constructor with the default Feign's encoder as a delegate. + */ + public FormEncoder () { + this(new Encoder.Default()); + } + + /** + * Constructor with specified delegate encoder. + * + * @param delegate delegate encoder, if this encoder couldn't encode object. + */ + public FormEncoder (Encoder delegate) { + this.delegate = delegate; + + val list = asList(new MultipartFormContentProcessor(delegate), + new UrlencodedFormContentProcessor()); + + processors = new HashMap(list.size(), 1.F); + for (ContentProcessor processor : list) { + processors.put(processor.getSupportedContentType(), processor); } + } - /** - * {@code FormEncoder} constructor with delegate encoder argument. - *

- * @param delegate delegate encoder for processing non-form requests. - */ - public FormEncoder (Encoder delegate) { - this.deligate = delegate; - processors = new HashMap(2, 1.F); - - val formEncodedDataProcessor = new FormEncodedDataProcessor(); - processors.put(formEncodedDataProcessor.getSupportetContentType().toLowerCase(), - formEncodedDataProcessor); - - val multipartEncodedDataProcessor = new MultipartEncodedDataProcessor(); - processors.put(multipartEncodedDataProcessor.getSupportetContentType().toLowerCase(), - multipartEncodedDataProcessor); + @Override + @SuppressWarnings("unchecked") + public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException { + val contentTypeValue = getContentTypeValue(template.headers()); + val contentType = ContentType.of(contentTypeValue); + if (!MAP_STRING_WILDCARD.equals(bodyType) || !processors.containsKey(contentType)) { + delegate.encode(object, bodyType, template); + return; } - @Override - public void encode (Object object, Type bodyType, RequestTemplate template) { - if (!MAP_STRING_WILDCARD.equals(bodyType)) { - deligate.encode(object, bodyType, template); - return; - } + val charset = getCharset(contentTypeValue); + val data = (Map) object; + try { + processors.get(contentType).process(template, charset, data); + } catch (Exception ex) { + throw new EncodeException(ex.getMessage()); + } + } - String formType = ""; - for (Map.Entry> entry : template.headers().entrySet()) { - if (!entry.getKey().equalsIgnoreCase("Content-Type")) { - continue; - } - for (String contentType : entry.getValue()) { - if (contentType != null && processors.containsKey(contentType.toLowerCase())) { - formType = contentType; - break; - } - } - if (!formType.isEmpty()) { - break; - } - } + /** + * Returns {@link ContentProcessor} for specific {@link ContentType}. + * + * @param type a type for content processor search. + * + * @return {@link ContentProcessor} instance for specified type or null. + */ + protected final ContentProcessor getContentProcessor (ContentType type) { + return processors.get(type); + } - if (formType.isEmpty()) { - deligate.encode(object, bodyType, template); - return; + private String getContentTypeValue (Map> headers) { + for (val entry : headers.entrySet()) { + if (!entry.getKey().equalsIgnoreCase(CONTENT_TYPE_HEADER)) { + continue; + } + for (val contentTypeValue : entry.getValue()) { + if (contentTypeValue == null) { + continue; } - - @SuppressWarnings("unchecked") - Map data = (Map) object; - processors.get(formType).process(data, template); + return contentTypeValue; + } } + return null; + } + + private Charset getCharset (String contentTypeValue) { + val matcher = CHARSET_PATTERN.matcher(contentTypeValue); + return matcher.find() + ? Charset.forName(matcher.group(1)) + : DEFAULT_CHARSET; + } } diff --git a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java b/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java deleted file mode 100644 index ba069bb90..000000000 --- a/feign-form/src/main/java/feign/form/MultipartEncodedDataProcessor.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2017 Artem Labazin . - * - * 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 feign.form; - -import static feign.Util.UTF_8; - -import feign.RequestTemplate; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.URLConnection; -import java.util.Map; -import lombok.SneakyThrows; -import lombok.val; - -/** - * Multipart form data implementation of {@link feign.form.FormDataProcessor}. - * - * @author Artem Labazin - * @since 30.04.2016 - */ -public class MultipartEncodedDataProcessor implements FormDataProcessor { - - public static final String CONTENT_TYPE; - - private static final String CRLF; - - static { - CONTENT_TYPE = "multipart/form-data"; - CRLF = "\r\n"; - } - - @Override - @SneakyThrows - public void process (Map data, RequestTemplate template) { - val boundary = createBoundary(); - val outputStream = new ByteArrayOutputStream(); - - try { - val writer = new PrintWriter(outputStream); - for (Map.Entry entry : data.entrySet()) { - writer.append("--" + boundary).append(CRLF); - if (isPayload(entry.getValue())) { - writeByteOrFile(outputStream, writer, entry.getKey(), entry.getValue()); - } else { - writeParameter(writer, entry.getKey(), entry.getValue().toString()); - } - writer.append(CRLF).flush(); - } - - writer.append("--" + boundary + "--").append(CRLF).flush(); - } catch (Throwable throwable) { - try { - outputStream.close(); - } catch (IOException ex) { - } - throw throwable; - } - - val contentType = new StringBuilder() - .append(CONTENT_TYPE) - .append("; boundary=") - .append(boundary) - .toString(); - - template.header("Content-Type", contentType); - template.body(outputStream.toByteArray(), UTF_8); - outputStream.close(); - } - - @Override - public String getSupportetContentType () { - return CONTENT_TYPE; - } - - /** - * Checks is passed object a supported file's type or not. - * - * @param value form file parameter. - */ - protected boolean isPayload (Object value) { - return value != null && (value instanceof File || value instanceof byte[]); - } - - /** - * Writes file's content to output stream. - * - * @param output output stream to remote destination. - * @param writer wrapped output stream. - * @param name the name of the file. - * @param value file's content. Byte array or {@link File}. - */ - protected void writeByteOrFile (OutputStream output, PrintWriter writer, String name, Object value) { - if (value instanceof byte[]) { - writeByteArray(output, writer, name, null, null, (byte[]) value); - } else { - writeFile(output, writer, name, null, (File) value); - } - } - - private String createBoundary () { - return Long.toHexString(System.currentTimeMillis()); - } - - private void writeParameter (PrintWriter writer, String name, String value) { - writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(CRLF); - writer.append("Content-Type: text/plain; charset=UTF-8").append(CRLF); - writer.append(CRLF).append(value); - } - - /** - * Writes file to output stream as a {@link File}. - * - * @param output output stream to remote destination. - * @param writer wrapped output stream. - * @param name the name of the file. - * @param contentType the content type (if known). - * @param file file object. - */ - @SneakyThrows - protected void writeFile (OutputStream output, PrintWriter writer, String name, String contentType, File file) { - writeFileMeta(writer, name, file.getName(), contentType); - - InputStream input = null; - try { - input = new FileInputStream(file); - byte[] buffer = new byte[1024]; - int length; - while ((length = input.read(buffer)) > 0) { - output.write(buffer, 0, length); - } - } finally { - if (input != null) { - input.close(); - } - } - writer.flush(); - } - - /** - * Writes file's content to output stream as a byte array. - * - * @param output utput stream to remote destination. - * @param writer wrapped output stream. - * @param name the name of the file. - * @param originalFilename the original filename (as on the client's machine). - * @param contentType the content type (if known). - * @param bytes file's content. - */ - @SneakyThrows - protected void writeByteArray (OutputStream output, - PrintWriter writer, - String name, - String originalFilename, - String contentType, - byte[] bytes - ) { - writeFileMeta(writer, name, originalFilename, contentType); - output.write(bytes); - writer.flush(); - } - - private void writeFileMeta (PrintWriter writer, String name, String fileName, String contentValue) { - val contentDesposition = new StringBuilder() - .append("Content-Disposition: form-data; name=\"").append(name).append("\"; ") - .append("filename=\"").append(fileName).append("\"") - .toString(); - - if (contentValue == null) { - contentValue = fileName != null - ? URLConnection.guessContentTypeFromName(fileName) - : "application/octet-stream"; - } - val contentType = new StringBuilder() - .append("Content-Type: ") - .append(contentValue) - .toString(); - - writer.append(contentDesposition).append(CRLF); - writer.append(contentType).append(CRLF); - writer.append("Content-Transfer-Encoding: binary").append(CRLF); - writer.append(CRLF).flush(); - } -} diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java new file mode 100644 index 000000000..508aeeef6 --- /dev/null +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form; + +import static feign.form.ContentType.MULTIPART; +import static lombok.AccessLevel.PRIVATE; + +import feign.RequestTemplate; +import feign.codec.Encoder; +import feign.form.multipart.ByteArrayWriter; +import feign.form.multipart.DelegateWriter; +import feign.form.multipart.ManyFilesWriter; +import feign.form.multipart.Output; +import feign.form.multipart.ParameterWriter; +import feign.form.multipart.SingleFileWriter; +import feign.form.multipart.Writer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.experimental.FieldDefaults; +import lombok.val; + +/** + * + * @author Artem Labazin + */ +@FieldDefaults(level = PRIVATE, makeFinal = true) +public class MultipartFormContentProcessor implements ContentProcessor { + + List writers; + + Writer defaultPerocessor; + + /** + * Constructor with specific delegate encoder. + * + * @param delegate specific delegate encoder for cases, when this processor couldn't handle request parameter. + */ + public MultipartFormContentProcessor (Encoder delegate) { + writers = new ArrayList(6); + addWriter(new ByteArrayWriter()); + addWriter(new SingleFileWriter()); + addWriter(new ManyFilesWriter()); + addWriter(new ParameterWriter()); + + defaultPerocessor = new DelegateWriter(delegate); + } + + @Override + public void process (RequestTemplate template, Charset charset, Map data) throws Exception { + val boundary = Long.toHexString(System.currentTimeMillis()); + val output = new Output(charset); + + for (val entry : data.entrySet()) { + val writer = findApplicableWriter(entry.getValue()); + writer.write(output, boundary, entry.getKey(), entry.getValue()); + } + + output.write("--").write(boundary).write("--").write(CRLF); + + val contentTypeHeaderValue = new StringBuilder() + .append(getSupportedContentType().getHeader()) + .append("; charset=").append(charset.name()) + .append("; boundary=").append(boundary) + .toString(); + + template.header(CONTENT_TYPE_HEADER, contentTypeHeaderValue); + template.body(output.toByteArray(), charset); + + output.close(); + } + + @Override + public ContentType getSupportedContentType () { + return MULTIPART; + } + + /** + * Adds {@link Writer} instance in runtime. + * + * @param writer additional writer. + */ + public final void addWriter (Writer writer) { + writers.add(writer); + } + + private Writer findApplicableWriter (Object value) { + for (val writer : writers) { + if (writer.isApplicable(value)) { + return writer; + } + } + return defaultPerocessor; + } +} diff --git a/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java new file mode 100644 index 000000000..688866bd8 --- /dev/null +++ b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form; + +import static feign.form.ContentType.URLENCODED; + +import feign.RequestTemplate; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.util.Map; +import lombok.SneakyThrows; +import lombok.val; + +/** + * + * @author Artem Labazin + */ +public class UrlencodedFormContentProcessor implements ContentProcessor { + + @Override + public void process (RequestTemplate template, Charset charset, Map data) throws Exception { + val body = new StringBuilder(); + for (val entry : data.entrySet()) { + if (body.length() > 0) { + body.append('&'); + } + body.append(createKeyValuePair(entry, charset)); + } + + val contentTypeValue = new StringBuilder() + .append(getSupportedContentType().getHeader()) + .append("; charset=").append(charset.name()) + .toString(); + + template.header(CONTENT_TYPE_HEADER, contentTypeValue); + template.body(body.toString().getBytes(charset), charset); + } + + @Override + public ContentType getSupportedContentType () { + return URLENCODED; + } + + @SneakyThrows + private String createKeyValuePair (Map.Entry entry, Charset charset) { + return new StringBuilder() + .append(URLEncoder.encode(entry.getKey(), charset.name())) + .append('=') + .append(URLEncoder.encode(entry.getValue().toString(), charset.name())) + .toString(); + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java new file mode 100644 index 000000000..1fbc3002f --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +import static feign.form.ContentProcessor.CRLF; + +import java.net.URLConnection; +import lombok.SneakyThrows; +import lombok.val; + +/** + * + * @author Artem Labazin + */ +public abstract class AbstractWriter implements Writer { + + @Override + public void write (Output output, String boundary, String key, Object value) throws Exception { + output.write("--").write(boundary).write(CRLF); + write(output, key, value); + output.write(CRLF); + } + + /** + * Writes data for it's children. + * + * @param output output writer. + * @param key name for piece of data. + * @param value piece of data. + */ + protected void write (Output output, String key, Object value) throws Exception { + } + + /** + * Writes file's metadata. + * + * @param output output writer. + * @param name name for piece of data. + * @param fileName file name. + * @param contentType type of file content. May be the {@code null}, in that case it will be determined by file name. + */ + @SneakyThrows + protected void writeFileMetadata (Output output, String name, String fileName, String contentType) { + val contentDesposition = new StringBuilder() + .append("Content-Disposition: form-data; name=\"").append(name).append("\"; ") + .append("filename=\"").append(fileName).append("\"") + .toString(); + + if (contentType == null) { + contentType = fileName != null + ? URLConnection.guessContentTypeFromName(fileName) + : "application/octet-stream"; + } + + val string = new StringBuilder() + .append(contentDesposition).append(CRLF) + .append("Content-Type: ").append(contentType).append(CRLF) + .append("Content-Transfer-Encoding: binary").append(CRLF) + .append(CRLF) + .toString(); + + output.write(string); + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java new file mode 100644 index 000000000..d21bdb619 --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +/** + * + * @author Artem Labazin + */ +public class ByteArrayWriter extends AbstractWriter { + + @Override + public boolean isApplicable (Object value) { + return value != null && value instanceof byte[]; + } + + @Override + protected void write (Output output, String key, Object value) throws Exception { + writeFileMetadata(output, key, null, null); + + byte[] bytes = (byte[]) value; + output.write(bytes); + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java new file mode 100644 index 000000000..3bc218c49 --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +import static lombok.AccessLevel.PRIVATE; + +import feign.RequestTemplate; +import feign.codec.Encoder; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.val; + +/** + * + * @author Artem Labazin + */ +@RequiredArgsConstructor +@FieldDefaults(level = PRIVATE, makeFinal = true) +public class DelegateWriter extends AbstractWriter { + + Encoder delegate; + + ParameterWriter parameterWriter = new ParameterWriter(); + + @Override + public boolean isApplicable (Object value) { + return true; + } + + @Override + protected void write (Output output, String key, Object value) throws Exception { + val fake = new RequestTemplate(); + delegate.encode(value, value.getClass(), fake); + val string = new String(fake.body(), output.getCharset()).replaceAll("\n", ""); + parameterWriter.write(output, key, string); + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java new file mode 100644 index 000000000..c5c5ccef9 --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +import static lombok.AccessLevel.PRIVATE; + +import java.io.File; +import lombok.experimental.FieldDefaults; +import lombok.val; + +/** + * + * @author Artem Labazin + */ +@FieldDefaults(level = PRIVATE, makeFinal = true) +public class ManyFilesWriter extends AbstractWriter { + + SingleFileWriter fileWriter = new SingleFileWriter(); + + @Override + public void write (Output output, String boundary, String key, Object value) throws Exception { + if (value instanceof File[]) { + val files = (File[]) value; + for (val file : files) { + fileWriter.write(output, boundary, key, file); + } + } else if (value instanceof Iterable) { + val iterable = (Iterable) value; + for (val file : iterable) { + fileWriter.write(output, boundary, key, file); + } + } + } + + @Override + public boolean isApplicable (Object value) { + if (value == null) { + return false; + } + if (value instanceof File[]) { + return true; + } + if (value instanceof Iterable) { + val iterable = (Iterable) value; + val iterator = iterable.iterator(); + if (iterator.hasNext() && iterator.next() instanceof File) { + return true; + } + } + return false; + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/Output.java b/feign-form/src/main/java/feign/form/multipart/Output.java new file mode 100644 index 000000000..08916cad1 --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/Output.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +import static lombok.AccessLevel.PRIVATE; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.nio.charset.Charset; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.experimental.FieldDefaults; + +/** + * + * @author Artem Labazin + */ +@RequiredArgsConstructor +@FieldDefaults(level = PRIVATE, makeFinal = true) +public class Output implements Closeable { + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @Getter + Charset charset; + + public Output write (String string) { + return write(string.getBytes(charset)); + } + + @SneakyThrows + public Output write (byte[] bytes) { + outputStream.write(bytes); + return this; + } + + @SneakyThrows + public Output write (byte[] bytes, int offset, int length) { + outputStream.write(bytes, offset, length); + return this; + } + + public byte[] toByteArray () { + return outputStream.toByteArray(); + } + + @Override + public void close () throws IOException { + outputStream.close(); + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java b/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java new file mode 100644 index 000000000..d42700b0c --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +import static feign.form.ContentProcessor.CRLF; + +import lombok.val; + +/** + * + * @author Artem Labazin + */ +public class ParameterWriter extends AbstractWriter { + + @Override + public boolean isApplicable (Object value) { + if (value == null) { + return false; + } + return value instanceof Number || + value instanceof String; + } + + @Override + protected void write (Output output, String key, Object value) throws Exception { + val string = new StringBuilder() + .append("Content-Disposition: form-data; name=\"").append(key).append('"').append(CRLF) + .append("Content-Type: text/plain; charset=").append(output.getCharset().name()).append(CRLF) + .append(CRLF) + .append(value.toString()) + .toString(); + + output.write(string); + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java new file mode 100644 index 000000000..513ed6d38 --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import lombok.val; + +/** + * + * @author Artem Labazin + */ +public class SingleFileWriter extends AbstractWriter { + + @Override + public boolean isApplicable (Object value) { + return value != null && value instanceof File; + } + + @Override + protected void write (Output output, String key, Object value) throws Exception { + val file = (File) value; + writeFileMetadata(output, key, file.getName(), null); + + InputStream input = null; + try { + input = new FileInputStream(file); + val buf = new byte[1024]; + int length; + while ((length = input.read(buf)) > 0) { + output.write(buf, 0, length); + } + } finally { + if (input != null) { + input.close(); + } + } + } +} diff --git a/feign-form/src/main/java/feign/form/multipart/Writer.java b/feign-form/src/main/java/feign/form/multipart/Writer.java new file mode 100644 index 000000000..d5819c154 --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/Writer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form.multipart; + +/** + * + * @author Artem Labazin + */ +public interface Writer { + + /** + * Processing form data to request body. + * + * @param output output writer. + * @param boundary data boundary. + * @param key name for piece of data. + * @param value piece of data. + */ + void write (Output output, String boundary, String key, Object value) throws Exception; + + /** + * Answers on question - "could this writer properly write the value". + * + * @param value object to write. + * + * @return {@code true} - if could write this object, otherwise {@code true} + */ + boolean isApplicable (Object value); +} diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index af82e1b5f..7c9c13326 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -13,29 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package feign.form; +import static feign.Logger.Level.FULL; +import static java.util.Arrays.asList; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + import feign.Feign; -import feign.Headers; -import feign.Param; -import feign.QueryMap; -import feign.RequestLine; -import feign.Response; -import feign.form.FormEncoder; import feign.jackson.JacksonEncoder; +import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.Map; +import lombok.val; import org.junit.Assert; import org.junit.Test; - -import static feign.Logger.Level.FULL; -import java.io.File; -import lombok.val; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @@ -51,72 +46,108 @@ ) public class BasicClientTest { - private static final TestApi api; - - static { - api = Feign.builder() - .encoder(new FormEncoder(new JacksonEncoder())) - .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) - .logLevel(FULL) - .target(TestApi.class, "http://localhost:8080"); - } - - @Test - public void testForm () { - val response = api.form("1", "1"); - - Assert.assertNotNull(response); - Assert.assertEquals(200, response.status()); - } - - @Test - public void testFormException () { - val response = api.form("1", "2"); - - Assert.assertNotNull(response); - Assert.assertEquals(400, response.status()); - } - - @Test - public void testUpload () throws Exception { - val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); - Assert.assertTrue(Files.exists(path)); - - val stringResponse = api.upload(10, Boolean.TRUE, path.toFile()); - Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); - } - - @Test - public void testJson () { - val dto = new Dto("Artem", 11); - val stringResponse = api.json(dto); - - Assert.assertEquals("ok", stringResponse); - } - - @Test - public void testQueryMap () { - Map value = Collections.singletonMap("filter", Arrays.asList("one", "two", "three", "four")); - - val stringResponse = api.queryMap(value); - Assert.assertEquals("4", stringResponse); - } - - interface TestApi { - - @RequestLine("POST /form") - @Headers("Content-Type: application/x-www-form-urlencoded") - Response form (@Param("key1") String key1, @Param("key2") String key2); - - @RequestLine("POST /upload/{id}") - @Headers("Content-Type: multipart/form-data") - String upload (@Param("id") Integer id, @Param("public") Boolean isPublic, @Param("file") File file); - - @RequestLine("POST /json") - @Headers("Content-Type: application/json") - String json (Dto dto); - - @RequestLine("POST /query_map") - String queryMap (@QueryMap Map value); - } + private static final TestClient api; + + static { + api = Feign.builder() + .encoder(new FormEncoder(new JacksonEncoder())) + .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) + .logLevel(FULL) + .target(TestClient.class, "http://localhost:8080"); + } + + @Test + public void testForm () { + val response = api.form("1", "1"); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.status()); + } + + @Test + public void testFormException () { + val response = api.form("1", "2"); + + Assert.assertNotNull(response); + Assert.assertEquals(400, response.status()); + } + + @Test + public void testUpload () throws Exception { + val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + Assert.assertTrue(Files.exists(path)); + + val stringResponse = api.upload(path.toFile()); + Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); + } + + @Test + public void testUploadWithParam () throws Exception { + val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + Assert.assertTrue(Files.exists(path)); + + val stringResponse = api.upload(10, Boolean.TRUE, path.toFile()); + Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); + } + + @Test + public void testJson () { + val dto = new Dto("Artem", 11); + val stringResponse = api.json(dto); + + Assert.assertEquals("ok", stringResponse); + } + + @Test + public void testQueryMap () { + Map value = Collections.singletonMap("filter", Arrays.asList("one", "two", "three", "four")); + + val stringResponse = api.queryMap(value); + Assert.assertEquals("4", stringResponse); + } + + @Test + public void testMultipleFilesArray () throws Exception { + val path1 = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + Assert.assertTrue(Files.exists(path1)); + val path2 = Paths.get(this.getClass().getClassLoader().getResource("another_file.txt").toURI()); + Assert.assertTrue(Files.exists(path2)); + + val stringResponse = api.uploadWithArray(new File[] { path1.toFile(), path2.toFile() }); + Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); + } + + @Test + public void testMultipleFilesList () throws Exception { + val path1 = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + Assert.assertTrue(Files.exists(path1)); + val path2 = Paths.get(this.getClass().getClassLoader().getResource("another_file.txt").toURI()); + Assert.assertTrue(Files.exists(path2)); + + val stringResponse = api.uploadWithList(asList(path1.toFile(), path2.toFile())); + Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); + } + +// @Test + public void testMultipleManyFiles () throws Exception { + val path1 = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + Assert.assertTrue(Files.exists(path1)); + val path2 = Paths.get(this.getClass().getClassLoader().getResource("another_file.txt").toURI()); + Assert.assertTrue(Files.exists(path2)); + + val stringResponse = api.uploadWithManyFiles(path1.toFile(), path2.toFile()); + Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); + } + + @Test + public void testUploadWithJson () throws Exception { + val dto = new Dto("Artem", 11); + + val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + Assert.assertTrue(Files.exists(path)); + + val response = api.uploadWithJson(dto, path.toFile()); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.status()); + } } diff --git a/feign-form/src/test/java/feign/form/Dto.java b/feign-form/src/test/java/feign/form/Dto.java index 964926dd7..4696dad32 100644 --- a/feign-form/src/test/java/feign/form/Dto.java +++ b/feign-form/src/test/java/feign/form/Dto.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -13,12 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package feign.form; +import static lombok.AccessLevel.PRIVATE; + import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; /** * @author Artem Labazin @@ -27,10 +31,12 @@ @Data @NoArgsConstructor @AllArgsConstructor +@FieldDefaults(level = PRIVATE) public class Dto implements Serializable { - private static final long serialVersionUID = 4743133513526293872L; + private static final long serialVersionUID = 4743133513526293872L; + + String name; - private String name; - private Integer age; + Integer age; } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 2cd78b34e..06338a6fd 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package feign.form; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -23,17 +24,24 @@ import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.util.List; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.multipart.MultipartFile; @@ -45,65 +53,111 @@ @SpringBootApplication public class Server { - @RequestMapping(value = "/form", method = POST) - public ResponseEntity form (@RequestParam("key1") String key1, - @RequestParam("key2") String key2 - ) { - HttpStatus status = !key1.equals(key2) - ? BAD_REQUEST - : OK; - return ResponseEntity.status(status).body(null); + @Autowired + private ObjectMapper objectMapper; + + @RequestMapping(value = "/form", method = POST) + public ResponseEntity form (@RequestParam("key1") String key1, + @RequestParam("key2") String key2 + ) { + val status = !key1.equals(key2) + ? BAD_REQUEST + : OK; + return ResponseEntity.status(status).body(null); + } + + @RequestMapping(value = "/upload/{id}", method = POST) + @ResponseStatus(OK) + public ResponseEntity upload (@PathVariable("id") Integer id, + @RequestParam("public") Boolean isPublic, + @RequestParam("file") MultipartFile file + ) { + HttpStatus status; + if (id == null || id != 10) { + status = LOCKED; + } else if (isPublic == null || !isPublic) { + status = FORBIDDEN; + } else if (file.getSize() == 0) { + status = I_AM_A_TEAPOT; + } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { + status = CONFLICT; + } else { + status = OK; } + return ResponseEntity.status(status).body(file.getSize()); + } - @RequestMapping(value = "/upload/{id}", method = POST) - @ResponseStatus(OK) - public ResponseEntity upload (@PathVariable("id") Integer id, - @RequestParam("public") Boolean isPublic, - @RequestParam("file") MultipartFile file - ) { - HttpStatus status; - if (id == null || id != 10) { - status = LOCKED; - } else if (isPublic == null || !isPublic) { - status = FORBIDDEN; - } else if (file.getSize() == 0) { - status = I_AM_A_TEAPOT; - } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { - status = CONFLICT; - } else { - status = OK; - } - return ResponseEntity.status(status).body(file.getSize()); + @RequestMapping(value = "/upload", method = POST) + public ResponseEntity upload (@RequestParam("file") MultipartFile file) { + HttpStatus status; + if (file.getSize() == 0) { + status = I_AM_A_TEAPOT; + } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { + status = CONFLICT; + } else { + status = OK; } + return ResponseEntity.status(status).body(file.getSize()); + } - @RequestMapping(value = "/json", method = POST, consumes = APPLICATION_JSON_VALUE) - public ResponseEntity json (@RequestBody Dto dto) { - HttpStatus status; - if (!dto.getName().equals("Artem")) { - status = CONFLICT; - } else if (!dto.getAge().equals(11)) { - status = I_AM_A_TEAPOT; - } else { - status = OK; - } - return ResponseEntity.status(status).body("ok"); + @RequestMapping(value = "/upload/files", method = POST) + public ResponseEntity upload (@RequestParam("files") MultipartFile[] files) { + HttpStatus status; + if (files[0].getSize() == 0 || files[1].getSize() == 0) { + status = I_AM_A_TEAPOT; + } else if (files[0].getOriginalFilename() == null || files[0].getOriginalFilename().trim().isEmpty() || + files[1].getOriginalFilename() == null || files[1].getOriginalFilename().trim().isEmpty()) { + status = CONFLICT; + } else { + status = OK; } + return ResponseEntity.status(status).body(files[0].getSize() + files[1].getSize()); + } - @RequestMapping(value = "/query_map") - public ResponseEntity queryMap (@RequestParam("filter") List filters) { - HttpStatus status; - if (filters == null || filters.isEmpty()) { - status = BAD_REQUEST; - } else { - status = OK; - } - return ResponseEntity.status(status).body(filters.size()); + @RequestMapping(value = "/json", method = POST, consumes = APPLICATION_JSON_VALUE) + public ResponseEntity json (@RequestBody Dto dto) { + HttpStatus status; + if (!dto.getName().equals("Artem")) { + status = CONFLICT; + } else if (!dto.getAge().equals(11)) { + status = I_AM_A_TEAPOT; + } else { + status = OK; } + return ResponseEntity.status(status).body("ok"); + } - @RequestMapping(value = "/wild-card-map", method = POST, consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity wildCardMap(@RequestParam("key1") String key1, - @RequestParam("key2") String key2) { - HttpStatus status = key1.equals(key2) ? OK : BAD_REQUEST; - return ResponseEntity.status(status).body(null); + @RequestMapping(value = "/query_map") + public ResponseEntity queryMap (@RequestParam("filter") List filters) { + HttpStatus status; + if (filters == null || filters.isEmpty()) { + status = BAD_REQUEST; + } else { + status = OK; } + return ResponseEntity.status(status).body(filters.size()); + } + + @RequestMapping(value = "/wild-card-map", method = POST, consumes = APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity wildCardMap (@RequestParam("key1") String key1, + @RequestParam("key2") String key2) { + val status = key1.equals(key2) + ? OK + : BAD_REQUEST; + return ResponseEntity.status(status).body(null); + } + + @PostMapping( + path = "/upload/with_json", + consumes = MULTIPART_FORM_DATA_VALUE + ) + public ResponseEntity uploadWithJson (@RequestPart("dto") String dtoString, + @RequestPart("file") MultipartFile file + ) throws IOException { + val dto = objectMapper.readValue(dtoString, Dto.class); + val status = dto != null && dto.getName().equals("Artem") + ? OK + : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(file.getSize()); + } } diff --git a/feign-form/src/test/java/feign/form/TestClient.java b/feign-form/src/test/java/feign/form/TestClient.java new file mode 100644 index 000000000..983070d5f --- /dev/null +++ b/feign-form/src/test/java/feign/form/TestClient.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form; + +import feign.Headers; +import feign.Param; +import feign.QueryMap; +import feign.RequestLine; +import feign.Response; +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + * + * @author Artem Labazin + */ +public interface TestClient { + + @RequestLine("POST /form") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response form (@Param("key1") String key1, @Param("key2") String key2); + + @RequestLine("POST /upload/{id}") + @Headers("Content-Type: multipart/form-data") + String upload (@Param("id") Integer id, @Param("public") Boolean isPublic, @Param("file") File file); + + @RequestLine("POST /upload") + @Headers("Content-Type: multipart/form-data") + String upload (@Param("file") File file); + + @RequestLine("POST /json") + @Headers("Content-Type: application/json") + String json (Dto dto); + + @RequestLine("POST /query_map") + String queryMap (@QueryMap Map value); + + @RequestLine("POST /upload/files") + @Headers("Content-Type: multipart/form-data") + String uploadWithArray (@Param("files") File[] files); + + @RequestLine("POST /upload/files") + @Headers("Content-Type: multipart/form-data") + String uploadWithList (@Param("files") List files); + + @RequestLine("POST /upload/files") + @Headers("Content-Type: multipart/form-data") + String uploadWithManyFiles (@Param("files") File file1, @Param("files") File file2); + + @RequestLine("POST /upload/with_json") + @Headers("Content-Type: multipart/form-data") + Response uploadWithJson (@Param("dto") Dto dto, @Param("file") File file); +} diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index 897388ef4..0051e7a19 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form; import feign.*; @@ -16,40 +32,40 @@ @RunWith(SpringRunner.class) @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class + webEnvironment = DEFINED_PORT, + classes = Server.class ) public class WildCardMapTest { - private static FormUrlEncodedApi API; - - @BeforeClass - public static void configureClient() { - API = Feign.builder() - .encoder(new FormEncoder()) - .logger(new Logger.JavaLogger().appendToFile("log.txt")) - .logLevel(FULL) - .target(FormUrlEncodedApi.class, "http://localhost:8080"); - } - - @Test - public void testOk() { - Map param = new HashMap() {{put("key1", "1"); put("key2", "1");}}; - Response response = API.wildCardMap(param); - Assert.assertEquals(200, response.status()); - } - - @Test - public void testBadRequest() { - Map param = new HashMap() {{put("key1", "1"); put("key2", "2");}}; - Response response = API.wildCardMap(param); - Assert.assertEquals(400, response.status()); - } - - interface FormUrlEncodedApi { - - @RequestLine("POST /wild-card-map") - @Headers("Content-Type: application/x-www-form-urlencoded") - Response wildCardMap(Map param); - } + private static FormUrlEncodedApi API; + + @BeforeClass + public static void configureClient() { + API = Feign.builder() + .encoder(new FormEncoder()) + .logger(new Logger.JavaLogger().appendToFile("log.txt")) + .logLevel(FULL) + .target(FormUrlEncodedApi.class, "http://localhost:8080"); + } + + @Test + public void testOk() { + Map param = new HashMap() {{put("key1", "1"); put("key2", "1");}}; + Response response = API.wildCardMap(param); + Assert.assertEquals(200, response.status()); + } + + @Test + public void testBadRequest() { + Map param = new HashMap() {{put("key1", "1"); put("key2", "2");}}; + Response response = API.wildCardMap(param); + Assert.assertEquals(400, response.status()); + } + + interface FormUrlEncodedApi { + + @RequestLine("POST /wild-card-map") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response wildCardMap(Map param); + } } diff --git a/pom.xml b/pom.xml index 8be841c1b..e6b2bfef1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,20 @@ + + @@ -7,13 +23,13 @@ io.github.openfeign.form parent - 2.2.1 + 3.0.0 pom org.springframework.boot spring-boot-starter-parent - 1.5.3.RELEASE + 1.5.8.RELEASE @@ -28,7 +44,6 @@ 1.8 1.8 3.2.0 - 2.6 Open Feign Forms Parent @@ -50,7 +65,7 @@ https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 2.2.1 + 3.0.0 @@ -126,7 +141,7 @@ maven-jar-plugin - ${maven-jar-plugin.version} + 3.0.2 ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -172,7 +187,7 @@ maven-compiler-plugin - + default-compile compile @@ -190,7 +205,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.15 + 1.16 signature-check From e62bc6dbb712b688bf35828daa14708d8948e7c7 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 27 Nov 2017 11:25:47 +0300 Subject: [PATCH 38/93] Refactoring --- .../src/main/java/feign/form/FormEncoder.java | 3 +- .../form/MultipartFormContentProcessor.java | 9 ++ .../java/feign/form/CustomClientTest.java | 89 +++++++++++++++++++ .../src/test/java/feign/form/Server.java | 43 +++++---- .../test/java/feign/form/WildCardMapTest.java | 2 +- 5 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 feign-form/src/test/java/feign/form/CustomClientTest.java diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index c4b651bb1..884b59b31 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -28,7 +28,6 @@ import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; -import lombok.SneakyThrows; import lombok.experimental.FieldDefaults; import lombok.val; @@ -105,7 +104,7 @@ public void encode (Object object, Type bodyType, RequestTemplate template) thro * * @return {@link ContentProcessor} instance for specified type or null. */ - protected final ContentProcessor getContentProcessor (ContentType type) { + public final ContentProcessor getContentProcessor (ContentType type) { return processors.get(type); } diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index 508aeeef6..e7014f1b6 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -30,6 +30,7 @@ import feign.form.multipart.Writer; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import lombok.experimental.FieldDefaults; @@ -99,6 +100,14 @@ public final void addWriter (Writer writer) { writers.add(writer); } + public final List getWriters () { + return Collections.unmodifiableList(writers); + } + + public final void setWriter (int index, Writer writer) { + writers.set(index, writer); + } + private Writer findApplicableWriter (Object value) { for (val writer : writers) { if (writer.isApplicable(value)) { diff --git a/feign-form/src/test/java/feign/form/CustomClientTest.java b/feign-form/src/test/java/feign/form/CustomClientTest.java new file mode 100644 index 000000000..bf3f8a583 --- /dev/null +++ b/feign-form/src/test/java/feign/form/CustomClientTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Artem Labazin . + * + * 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 feign.form; + +import static feign.form.ContentType.MULTIPART; +import static feign.Logger.Level.FULL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +import feign.Feign; +import feign.form.multipart.ByteArrayWriter; +import feign.form.multipart.Output; +import feign.Headers; +import feign.jackson.JacksonEncoder; +import feign.Param; +import feign.RequestLine; +import lombok.val; +import org.junit.runner.RunWith; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Artem Labazin + * @since 27.11.2017 + */ +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = DEFINED_PORT, + classes = Server.class +) +public class CustomClientTest { + + private static final CustomClient API; + + static { + val encoder = new FormEncoder(new JacksonEncoder()); + val processor = (MultipartFormContentProcessor) encoder.getContentProcessor(MULTIPART); + processor.setWriter(0, new CustomByteArrayWriter()); + + + API = Feign.builder() + .encoder(encoder) + .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) + .logLevel(FULL) + .target(CustomClient.class, "http://localhost:8080"); + } + + @Test + public void test () { + val stringResponse = API.uploadByteArray(new byte[0]); + + assertNotNull(stringResponse); + assertEquals("popa.txt", stringResponse); + } + + private static class CustomByteArrayWriter extends ByteArrayWriter { + + @Override + protected void write (Output output, String key, Object value) throws Exception { + writeFileMetadata(output, key, "popa.txt", null); + + val bytes = (byte[]) value; + output.write(bytes); + } + } + + public interface CustomClient { + + @RequestLine("POST /upload/byte_array") + @Headers("Content-Type: multipart/form-data") + String uploadByteArray (@Param("file") byte[] bytes); + } +} diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 06338a6fd..7a366a1c1 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -58,19 +58,19 @@ public class Server { @RequestMapping(value = "/form", method = POST) public ResponseEntity form (@RequestParam("key1") String key1, - @RequestParam("key2") String key2 + @RequestParam("key2") String key2 ) { val status = !key1.equals(key2) - ? BAD_REQUEST - : OK; + ? BAD_REQUEST + : OK; return ResponseEntity.status(status).body(null); } @RequestMapping(value = "/upload/{id}", method = POST) @ResponseStatus(OK) public ResponseEntity upload (@PathVariable("id") Integer id, - @RequestParam("public") Boolean isPublic, - @RequestParam("file") MultipartFile file + @RequestParam("public") Boolean isPublic, + @RequestParam("file") MultipartFile file ) { HttpStatus status; if (id == null || id != 10) { @@ -129,21 +129,17 @@ public ResponseEntity json (@RequestBody Dto dto) { @RequestMapping(value = "/query_map") public ResponseEntity queryMap (@RequestParam("filter") List filters) { - HttpStatus status; - if (filters == null || filters.isEmpty()) { - status = BAD_REQUEST; - } else { - status = OK; - } + val status = filters != null && !filters.isEmpty() + ? OK + : I_AM_A_TEAPOT; return ResponseEntity.status(status).body(filters.size()); } @RequestMapping(value = "/wild-card-map", method = POST, consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity wildCardMap (@RequestParam("key1") String key1, - @RequestParam("key2") String key2) { + public ResponseEntity wildCardMap (@RequestParam("key1") String key1, @RequestParam("key2") String key2) { val status = key1.equals(key2) - ? OK - : BAD_REQUEST; + ? OK + : I_AM_A_TEAPOT; return ResponseEntity.status(status).body(null); } @@ -152,12 +148,23 @@ public ResponseEntity wildCardMap (@RequestParam("key1") String key1, consumes = MULTIPART_FORM_DATA_VALUE ) public ResponseEntity uploadWithJson (@RequestPart("dto") String dtoString, - @RequestPart("file") MultipartFile file + @RequestPart("file") MultipartFile file ) throws IOException { val dto = objectMapper.readValue(dtoString, Dto.class); val status = dto != null && dto.getName().equals("Artem") - ? OK - : I_AM_A_TEAPOT; + ? OK + : I_AM_A_TEAPOT; return ResponseEntity.status(status).body(file.getSize()); } + + @PostMapping( + path = "/upload/byte_array", + consumes = MULTIPART_FORM_DATA_VALUE + ) + public ResponseEntity uploadByteArray (@RequestPart("file") MultipartFile file) { + val status = file != null + ? OK + : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(file.getOriginalFilename()); + } } diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index 0051e7a19..a4c031bd6 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -59,7 +59,7 @@ public void testOk() { public void testBadRequest() { Map param = new HashMap() {{put("key1", "1"); put("key2", "2");}}; Response response = API.wildCardMap(param); - Assert.assertEquals(400, response.status()); + Assert.assertEquals(418, response.status()); } interface FormUrlEncodedApi { From 1b3ac6365212ecf3e7b85e0a40a31a95f65efcbb Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 27 Nov 2017 11:27:38 +0300 Subject: [PATCH 39/93] Update version --- README.md | 6 +++--- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6b9175ad3..194173ed8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.0 + 3.0.1 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.0 + 3.0.1 io.github.openfeign.form feign-form-spring - 3.0.0 + 3.0.1 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 729001b6f..d808bd5e8 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.0 + 3.0.1 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 888d5bae4..b9b079a0f 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.0 + 3.0.1 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index e6b2bfef1..77479828e 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.0 + 3.0.1 pom @@ -65,7 +65,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.0.0 + 3.0.1 From dfed8c65a78cf1c70d07e60123f32db372a9b38e Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Thu, 30 Nov 2017 18:54:14 +0300 Subject: [PATCH 40/93] Issue #18: Sets charset to null in RequestTemplate body for compatibility with third-party clients --- README.md | 6 +++--- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- .../main/java/feign/form/MultipartFormContentProcessor.java | 4 +++- pom.xml | 6 +++--- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 194173ed8..797ec4453 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.1 + 3.0.2 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.1 + 3.0.2 io.github.openfeign.form feign-form-spring - 3.0.1 + 3.0.2 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index d808bd5e8..afa499770 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.1 + 3.0.2 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index b9b079a0f..599b6f9f4 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.1 + 3.0.2 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index e7014f1b6..59eee8768 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -81,7 +81,9 @@ public void process (RequestTemplate template, Charset charset, Mapio.github.openfeign.form parent - 3.0.1 + 3.0.2 pom @@ -65,7 +65,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.0.1 + 3.0.2 @@ -155,7 +155,7 @@ limitations under the License. org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.0.2 attach-sources From 6e84ad15a7dfb2463a70ef2bb04acf3c4652587d Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Thu, 30 Nov 2017 18:59:37 +0300 Subject: [PATCH 41/93] Fix maven-jar-plugin version --- README.md | 6 +++--- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- pom.xml | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 797ec4453..0cfb3c179 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.2 + 3.0.3 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.2 + 3.0.3 io.github.openfeign.form feign-form-spring - 3.0.2 + 3.0.3 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index afa499770..0a4ef9358 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.2 + 3.0.3 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 599b6f9f4..99ecdd1bd 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.2 + 3.0.3 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index 17d01e431..7a5b972cc 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.0.2 + 3.0.3 pom @@ -65,7 +65,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.0.2 + 3.0.3 @@ -155,7 +155,7 @@ limitations under the License. org.apache.maven.plugins maven-source-plugin - 3.0.2 + 3.0.1 attach-sources From 9ac39cfbae5bd72b90488a4654e7219e553b84eb Mon Sep 17 00:00:00 2001 From: Hao Tri Pham Date: Mon, 8 Jan 2018 19:45:31 +0100 Subject: [PATCH 42/93] multipart/form-data reader --- feign-form-spring/pom.xml | 7 + .../converter/ByteArrayMultipartFile.java | 78 ++++++++++ .../spring/converter/IgnoreKeyCaseMap.java | 80 ++++++++++ .../SpringManyMultipartFilesReader.java | 137 ++++++++++++++++++ .../SpringManyMultipartFilesReaderTest.java | 64 ++++++++ 5 files changed, 366 insertions(+) create mode 100644 feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java create mode 100644 feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java create mode 100644 feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 0a4ef9358..a54d6e2fe 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -44,6 +44,13 @@ limitations under the License. compile + + commons-fileupload + commons-fileupload + 1.3.3 + compile + + org.springframework.cloud spring-cloud-starter-feign diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java new file mode 100644 index 000000000..765bbc293 --- /dev/null +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java @@ -0,0 +1,78 @@ +package feign.form.spring.converter; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.util.Assert; +import org.springframework.web.multipart.MultipartFile; + +/** + * Straight-forward implementation of interface {@link MultipartFile} where the file + * data is held as a byte array in memory. + */ +final class ByteArrayMultipartFile implements MultipartFile { + + private final String name; + private final String originalFileName; + private final String contentType; + private final byte[] data; + + ByteArrayMultipartFile(final String name, final String originalFileName, + final String contentType, final byte[] data) { + Assert.notNull(data, "Byte array data may not be null!"); + + this.name = name; + this.originalFileName = originalFileName; + this.contentType = contentType; + this.data = data; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getOriginalFilename() { + return originalFileName; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return data.length == 0; + } + + @Override + public long getSize() { + return data.length; + } + + @Override + public byte[] getBytes() { + return data; + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(data); + } + + @Override + public void transferTo(final File destination) throws IOException { + final FileOutputStream fos = new FileOutputStream(destination); + try { + fos.write(data); + } finally { + fos.close(); + } + } + +} diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java new file mode 100644 index 000000000..f8c82db32 --- /dev/null +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java @@ -0,0 +1,80 @@ +package feign.form.spring.converter; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A Map implementation that normalizes the key to UPPER CASE, so + * that value retrieval via the key is case insensitive. + */ +final class IgnoreKeyCaseMap implements Map { + private final HashMap hashMap = new HashMap(); + + @Override + public int size() { + return hashMap.size(); + } + + @Override + public boolean isEmpty() { + return hashMap.isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + return hashMap.containsKey(normalizeKey(key)); + } + + @Override + public boolean containsValue(final Object value) { + return hashMap.containsValue(value); + } + + @Override + public String get(final Object key) { + return hashMap.get(normalizeKey(key)); + } + + @Override + public String put(final String key, final String value) { + return hashMap.put(normalizeKey(key), value); + } + + @Override + public String remove(final Object key) { + return hashMap.remove(normalizeKey(key)); + } + + @Override + public void putAll(final Map m) { + for (final Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + hashMap.clear(); + } + + @Override + public Set keySet() { + return hashMap.keySet(); + } + + @Override + public Collection values() { + return hashMap.values(); + } + + @Override + public Set> entrySet() { + return hashMap.entrySet(); + } + + private static String normalizeKey(final Object key) { + return key != null ? key.toString().toUpperCase() : null; + } +} diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java new file mode 100644 index 000000000..c906001ef --- /dev/null +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -0,0 +1,137 @@ +package feign.form.spring.converter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.fileupload.MultipartStream; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + + +/** + * Implementation of {@link HttpMessageConverter} that can read multipart/form-data HTTP bodies + * (writing is not handled because that is already supported by {@link FormHttpMessageConverter}). + * + *

+ * This reader supports an array of {@link MultipartFile} as the mapping return class type - each + * multipart body is read into an underlying byte array (in memory) implemented via + * {@link ByteArrayMultipartFile}. + */ + +public class SpringManyMultipartFilesReader extends AbstractHttpMessageConverter { + + private static final Pattern NEWLINES_PATTERN = Pattern.compile("\\R"); + private static final Pattern COLON_PATTERN = Pattern.compile(":"); + + private static final Pattern SEMICOLON_PATTERN = Pattern.compile(";"); + private static final Pattern EQUALITY_SIGN_PATTERN = Pattern.compile("="); + + private final int bufSize; + + public SpringManyMultipartFilesReader(final int bufSize) { + super(MediaType.MULTIPART_FORM_DATA); + this.bufSize = bufSize; + } + + @Override + protected boolean canWrite(final MediaType mediaType) { + return false; // Class NOT meant for writing multipart/form-data HTTP bodies + } + + @Override + protected boolean supports(final Class clazz) { + return MultipartFile[].class == clazz; + } + + @Override + protected MultipartFile[] readInternal(final Class clazz, + final HttpInputMessage inputMessage) throws IOException { + final byte[] boundary = getMultiPartBoundary(inputMessage.getHeaders().getContentType()); + final MultipartStream multipartStream = new MultipartStream(inputMessage.getBody(), boundary, bufSize, null); + + final List multiparts = new LinkedList(); + for (boolean nextPart = multipartStream.skipPreamble(); nextPart; nextPart = multipartStream.readBoundary()) { + try { + multiparts.add(readMultiPart(multipartStream)); + } catch (final Exception e) { + throw new HttpMessageNotReadableException("Multipart body could not be read.", e); + } + } + + return multiparts.toArray(new ByteArrayMultipartFile[multiparts.size()]); + } + + @Override + protected void writeInternal(final MultipartFile[] byteArrayMultipartFiles, final HttpOutputMessage outputMessage) { + throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support writing to HTTP body."); + } + + private byte[] getMultiPartBoundary(final MediaType contentType) { + final String boundaryStr = unquote(contentType.getParameter("boundary")); + if (!StringUtils.isEmpty(boundaryStr)) { + return boundaryStr.getBytes(); + } else { + throw new HttpMessageNotReadableException("Content-Type missing boundary information."); + } + } + + private ByteArrayMultipartFile readMultiPart(final MultipartStream multipartStream) throws IOException { + final IgnoreKeyCaseMap multiPartHeaders = splitIntoKeyValuePairs(multipartStream.readHeaders(), + NEWLINES_PATTERN, COLON_PATTERN, false); + + final String multipartContentType = multiPartHeaders.get(HttpHeaders.CONTENT_TYPE); + final IgnoreKeyCaseMap contentDisposition = splitIntoKeyValuePairs( + multiPartHeaders.get(HttpHeaders.CONTENT_DISPOSITION), SEMICOLON_PATTERN, EQUALITY_SIGN_PATTERN, true); + + if (!contentDisposition.containsKey("form-data")) { + throw new HttpMessageNotReadableException("Content-Disposition is not of type form-data."); + } + + final ByteArrayOutputStream bodyStream = new ByteArrayOutputStream(); + multipartStream.readBodyData(bodyStream); + + return new ByteArrayMultipartFile(contentDisposition.get("name"), contentDisposition.get("filename"), + multipartContentType, bodyStream.toByteArray()); + } + + private IgnoreKeyCaseMap splitIntoKeyValuePairs(final String str, + final Pattern entriesSeparatorPattern, + final Pattern keyValueSeparatorPattern, + final boolean unquoteValue) { + final IgnoreKeyCaseMap keyValuePairs = new IgnoreKeyCaseMap(); + if (!StringUtils.isEmpty(str)) { + final String[] entries = entriesSeparatorPattern.split(str); + for (final String entry : entries) { + final String[] pair = keyValueSeparatorPattern.split(entry.trim(), 2); + final String key = pair[0].trim(); + final String value = pair.length > 1 ? pair[1].trim() : ""; + keyValuePairs.put(key, unquoteValue ? unquote(value) : value); + } + } + return keyValuePairs; + } + + private String unquote(final String value) { + return value != null + ? (isSurroundedBy(value, "\"") || isSurroundedBy(value, "'") + ? value.substring(1, value.length() - 1) + : value) + : null; + } + + private boolean isSurroundedBy(final String value, final String preSuffix) { + return value.length() > 1 && value.startsWith(preSuffix) && value.endsWith(preSuffix); + } +} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java new file mode 100644 index 000000000..81d36586f --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java @@ -0,0 +1,64 @@ +package feign.form.feign.spring.converter; + +import feign.form.spring.converter.SpringManyMultipartFilesReader; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.MediaType; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static java.util.Collections.singletonList; + +public class SpringManyMultipartFilesReaderTest { + + private static final String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; + + @Test + public void readMultipartFormDataTest() throws IOException { + final SpringManyMultipartFilesReader multipartFilesReader = new SpringManyMultipartFilesReader(4096); + final MultipartFile[] multipartFiles = multipartFilesReader.read(MultipartFile[].class, new ValidMulitpartMessage()); + + Assert.assertEquals(2, multipartFiles.length); + + Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, multipartFiles[0].getContentType()); + Assert.assertEquals("form-item-1", multipartFiles[0].getName()); + Assert.assertFalse(multipartFiles[0].isEmpty()); + + Assert.assertEquals(MediaType.TEXT_PLAIN_VALUE, multipartFiles[1].getContentType()); + Assert.assertEquals("form-item-2-file-1", multipartFiles[1].getOriginalFilename()); + Assert.assertEquals("Plain text", IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")); + } + + public static class ValidMulitpartMessage implements HttpInputMessage { + @Override + public InputStream getBody() throws IOException { + final String multipartBody = "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + + "Content-Type: application/json\r\n" + + "Content-Disposition: form-data; name=\"form-item-1\"\r\n" + + "\r\n" + + "{\"id\":1}" + "\r\n" + + "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + + "content-type: text/plain\r\n" + + "content-disposition: Form-Data; Filename=\"form-item-2-file-1\"; Name=\"form-item-2\"\r\n" + + "\r\n" + + "Plain text" + "\r\n" + + "--" + DUMMY_MULTIPART_BOUNDARY + "--\r\n"; + + return new ByteArrayInputStream(multipartBody.getBytes("US-ASCII")); + } + + @Override + public HttpHeaders getHeaders() { + final HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.put(HttpHeaders.CONTENT_TYPE, + singletonList(MediaType.MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY)); + return httpHeaders; + } + } +} From 2276855c38e9e01cd198b56f5f648cd565348a6e Mon Sep 17 00:00:00 2001 From: Hao Tri Pham Date: Tue, 9 Jan 2018 13:32:44 +0100 Subject: [PATCH 43/93] multipart/form-data reader unit & integration test --- .../form/feign/spring/DownloadClient.java | 57 +++++++++++++++++++ .../java/feign/form/feign/spring/Server.java | 34 +++++++++++ .../spring/SpringMultipartDecoderTest.java | 47 +++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java new file mode 100644 index 000000000..690a45630 --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java @@ -0,0 +1,57 @@ +package feign.form.feign.spring; + +import feign.codec.Decoder; +import feign.form.spring.converter.SpringManyMultipartFilesReader; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.HttpMessageConverters; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.netflix.feign.support.SpringDecoder; +import org.springframework.context.annotation.Bean; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.List; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@FeignClient( + name = "multipart-download-support-service", + url = "http://localhost:8080", + configuration = DownloadClient.ClientConfiguration.class +) +public interface DownloadClient { + + @RequestMapping( + value = "/multipart/download/{fileId}", + method = GET + ) + MultipartFile[] download(@PathVariable("fileId") String fileId); + + class ClientConfiguration { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public Decoder feignDecoder () { + final List> springConverters = messageConverters.getObject().getConverters(); + final List> decoderConverters + = new ArrayList>(springConverters.size() + 1); + + decoderConverters.addAll(springConverters); + decoderConverters.add(new SpringManyMultipartFilesReader(4096)); + final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(decoderConverters); + + return new SpringDecoder(new ObjectFactory() { + @Override + public HttpMessageConverters getObject() { + return httpMessageConverters; + } + }); + } + } +} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 81bbd5a84..b64cfc979 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -16,13 +16,24 @@ package feign.form.feign.spring; +import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import java.nio.charset.Charset; import java.util.Map; + import lombok.SneakyThrows; +import org.apache.commons.codec.CharEncoding; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -88,4 +99,27 @@ public String upload4 (@PathVariable("id") String id, ) { return userName + ':' + id + ':' + map.size(); } + + @RequestMapping( + value = "/multipart/download/{fileId}", + method = GET, + produces = MULTIPART_FORM_DATA_VALUE + ) + public MultiValueMap download(@PathVariable("fileId") String fileId) { + MultiValueMap multiParts = new LinkedMultiValueMap(); + + String info = "The text for file ID " + fileId + ". Testing unicode €"; + HttpHeaders infoPartheader = new HttpHeaders(); + infoPartheader.setContentType(new MediaType("text", "plain", Charset.forName(CharEncoding.UTF_8))); + HttpEntity infoPart = new HttpEntity(info, infoPartheader); + + ClassPathResource file = new ClassPathResource("testfile.txt"); + HttpHeaders filePartheader = new HttpHeaders(); + filePartheader.setContentType(APPLICATION_OCTET_STREAM); + HttpEntity filePart = new HttpEntity(file, filePartheader); + + multiParts.add("info", infoPart); + multiParts.add("file", filePart); + return multiParts; + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java new file mode 100644 index 000000000..7da7b6a4a --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java @@ -0,0 +1,47 @@ +package feign.form.feign.spring; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.multipart.MultipartFile; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = DEFINED_PORT, + classes = Server.class, + properties = { + "server.port=8080", + "feign.hystrix.enabled=false" + } +) +public class SpringMultipartDecoderTest { + + @Autowired + private DownloadClient downloadClient; + + @Test + public void upload1Test () throws Exception { + MultipartFile[] downloads = downloadClient.download("123"); + + Assert.assertEquals(2, downloads.length); + + Assert.assertEquals("info", downloads[0].getName()); + MediaType infoContentType = MediaType.parseMediaType(downloads[0].getContentType()); + Assert.assertTrue(MediaType.TEXT_PLAIN.includes(infoContentType)); + Assert.assertNotNull(infoContentType.getCharset()); + Assert.assertEquals("The text for file ID 123. Testing unicode €", + IOUtils.toString(downloads[0].getInputStream(), infoContentType.getCharset().name())); + + Assert.assertEquals("testfile.txt", downloads[1].getOriginalFilename()); + Assert.assertEquals(MediaType.APPLICATION_OCTET_STREAM_VALUE, downloads[1].getContentType()); + Assert.assertEquals(13, downloads[1].getSize()); + } + +} From 876dcd11b9d8942d3cd75d7f3361c0c54e4d7869 Mon Sep 17 00:00:00 2001 From: Hao Tri Pham Date: Tue, 9 Jan 2018 14:19:55 +0100 Subject: [PATCH 44/93] multipart/form-data reader javadoc update --- .../spring/converter/SpringManyMultipartFilesReader.java | 6 ++++-- .../feign/form/feign/spring/SpringMultipartDecoderTest.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index c906001ef..4a68892c6 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -19,7 +19,6 @@ import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; - /** * Implementation of {@link HttpMessageConverter} that can read multipart/form-data HTTP bodies * (writing is not handled because that is already supported by {@link FormHttpMessageConverter}). @@ -29,7 +28,6 @@ * multipart body is read into an underlying byte array (in memory) implemented via * {@link ByteArrayMultipartFile}. */ - public class SpringManyMultipartFilesReader extends AbstractHttpMessageConverter { private static final Pattern NEWLINES_PATTERN = Pattern.compile("\\R"); @@ -40,6 +38,10 @@ public class SpringManyMultipartFilesReader extends AbstractHttpMessageConverter private final int bufSize; + /** + * Construct an {@code AbstractHttpMessageConverter} that can read mulitpart/form-data. + * @param bufSize The size of the buffer (in bytes) to read the HTTP multipart body. + */ public SpringManyMultipartFilesReader(final int bufSize) { super(MediaType.MULTIPART_FORM_DATA); this.bufSize = bufSize; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java index 7da7b6a4a..52d994665 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java @@ -27,7 +27,7 @@ public class SpringMultipartDecoderTest { private DownloadClient downloadClient; @Test - public void upload1Test () throws Exception { + public void downloadTest() throws Exception { MultipartFile[] downloads = downloadClient.download("123"); Assert.assertEquals(2, downloads.length); From b48cf01a42934b212f38dc2cf26208ddd6cb40fb Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 16 Jan 2018 11:57:10 +0300 Subject: [PATCH 45/93] Add checkstyle, PMD and FindBugs --- .codestyle/checkstyle.xml | 403 ++++++++++++++++++ .codestyle/findbugs.xml | 22 + .codestyle/pmd.xml | 162 +++++++ README.md | 6 +- feign-form-spring/pom.xml | 18 +- .../feign/form/spring/SpringFormEncoder.java | 8 +- .../SpringManyMultipartFilesWriter.java | 7 +- .../SpringSingleMultipartFileWriter.java | 9 +- .../java/feign/form/feign/spring/Client.java | 6 +- .../java/feign/form/feign/spring/Server.java | 4 +- .../feign/spring/SpringFormEncoderTest.java | 4 +- feign-form/pom.xml | 27 +- .../java/feign/form/ContentProcessor.java | 9 +- .../src/main/java/feign/form/ContentType.java | 8 +- .../src/main/java/feign/form/FormEncoder.java | 17 +- .../form/MultipartFormContentProcessor.java | 18 +- .../form/UrlencodedFormContentProcessor.java | 15 +- .../feign/form/multipart/AbstractWriter.java | 21 +- .../feign/form/multipart/ByteArrayWriter.java | 8 +- .../feign/form/multipart/DelegateWriter.java | 7 +- .../feign/form/multipart/ManyFilesWriter.java | 7 +- .../java/feign/form/multipart/Output.java | 36 +- .../feign/form/multipart/ParameterWriter.java | 6 +- .../form/multipart/SingleFileWriter.java | 9 +- .../java/feign/form/multipart/Writer.java | 6 +- .../test/java/feign/form/BasicClientTest.java | 6 +- .../java/feign/form/CustomClientTest.java | 6 +- feign-form/src/test/java/feign/form/Dto.java | 6 +- .../src/test/java/feign/form/Server.java | 6 +- .../src/test/java/feign/form/TestClient.java | 6 +- .../test/java/feign/form/WildCardMapTest.java | 4 +- pom.xml | 85 +++- 32 files changed, 853 insertions(+), 109 deletions(-) create mode 100644 .codestyle/checkstyle.xml create mode 100644 .codestyle/findbugs.xml create mode 100644 .codestyle/pmd.xml diff --git a/.codestyle/checkstyle.xml b/.codestyle/checkstyle.xml new file mode 100644 index 000000000..e06be59d7 --- /dev/null +++ b/.codestyle/checkstyle.xml @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.codestyle/findbugs.xml b/.codestyle/findbugs.xml new file mode 100644 index 000000000..eaed6b42c --- /dev/null +++ b/.codestyle/findbugs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/.codestyle/pmd.xml b/.codestyle/pmd.xml new file mode 100644 index 000000000..ce116cd99 --- /dev/null +++ b/.codestyle/pmd.xml @@ -0,0 +1,162 @@ + + + + + + PMD Rules + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 0cfb3c179..409b831e1 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.3 + 3.1.0 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.0.3 + 3.1.0 io.github.openfeign.form feign-form-spring - 3.0.3 + 3.1.0 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 0a4ef9358..4ddbd3038 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -1,6 +1,6 @@ + + + + From d75b5b8f373b4a9ec9844fefd013f81f542d0ca5 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 16 Jan 2018 14:02:23 +0300 Subject: [PATCH 46/93] Add tf-haotri-pham featur for files response --- README.md | 48 +++- feign-form-spring/pom.xml | 2 +- .../converter/ByteArrayMultipartFile.java | 100 ++++--- .../spring/converter/IgnoreKeyCaseMap.java | 132 +++++----- .../SpringManyMultipartFilesReader.java | 246 ++++++++++-------- .../form/feign/spring/DownloadClient.java | 17 +- .../java/feign/form/feign/spring/Server.java | 130 ++++----- .../spring/SpringMultipartDecoderTest.java | 2 +- .../SpringManyMultipartFilesReaderTest.java | 52 ++-- feign-form/pom.xml | 2 +- pom.xml | 4 +- 11 files changed, 408 insertions(+), 327 deletions(-) diff --git a/README.md b/README.md index 409b831e1..011307427 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.1.0 + 3.2.0 ... @@ -100,12 +100,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.1.0 + 3.2.0 io.github.openfeign.form feign-form-spring - 3.1.0 + 3.2.0 ... @@ -143,3 +143,45 @@ public interface FileUploadServiceClient extends IFileUploadServiceClient { } } ``` + +Thanks to [tf-haotri-pham](https://github.com/tf-haotri-pham) for his featur, which makes use of Apache commons-fileupload library, which handles the parsing of the multipart response. The body data parts are held as byte arrays in memory. + +To use this feature, include SpringManyMultipartFilesReader in the list of message converters for the Decoder and have the Feign client return an array of MultipartFile: + +```java +@FeignClient( + name = "${feign.name}", + url = "${feign.url}" + configuration = DownloadClient.ClientConfiguration.class) +public interface DownloadClient { + + @RequestMapping( + value = "/multipart/download/{fileId}", + method = GET) + MultipartFile[] download(@PathVariable("fileId") String fileId); + + class ClientConfiguration { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public Decoder feignDecoder () { + final List> springConverters = messageConverters.getObject().getConverters(); + final List> decoderConverters + = new ArrayList>(springConverters.size() + 1); + + decoderConverters.addAll(springConverters); + decoderConverters.add(new SpringManyMultipartFilesReader(4096)); + final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(decoderConverters); + + return new SpringDecoder(new ObjectFactory() { + @Override + public HttpMessageConverters getObject() { + return httpMessageConverters; + } + }); + } + } +} +``` diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 00816312d..950243782 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.1.0 + 3.2.0 Open Feign Forms Extension for Spring diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java index 765bbc293..24328ea42 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java @@ -1,3 +1,19 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.spring.converter; import java.io.ByteArrayInputStream; @@ -6,73 +22,49 @@ import java.io.IOException; import java.io.InputStream; -import org.springframework.util.Assert; +import lombok.NonNull; +import lombok.Value; +import lombok.val; import org.springframework.web.multipart.MultipartFile; /** * Straight-forward implementation of interface {@link MultipartFile} where the file * data is held as a byte array in memory. */ -final class ByteArrayMultipartFile implements MultipartFile { - - private final String name; - private final String originalFileName; - private final String contentType; - private final byte[] data; - - ByteArrayMultipartFile(final String name, final String originalFileName, - final String contentType, final byte[] data) { - Assert.notNull(data, "Byte array data may not be null!"); +@Value +class ByteArrayMultipartFile implements MultipartFile { - this.name = name; - this.originalFileName = originalFileName; - this.contentType = contentType; - this.data = data; - } - - @Override - public String getName() { - return name; - } + String name; - @Override - public String getOriginalFilename() { - return originalFileName; - } + String originalFilename; - @Override - public String getContentType() { - return contentType; - } + String contentType; - @Override - public boolean isEmpty() { - return data.length == 0; - } + @NonNull + byte[] bytes; - @Override - public long getSize() { - return data.length; - } + @Override + public boolean isEmpty () { + return bytes.length == 0; + } - @Override - public byte[] getBytes() { - return data; - } + @Override + public long getSize () { + return bytes.length; + } - @Override - public InputStream getInputStream() { - return new ByteArrayInputStream(data); - } + @Override + public InputStream getInputStream () { + return new ByteArrayInputStream(bytes); + } - @Override - public void transferTo(final File destination) throws IOException { - final FileOutputStream fos = new FileOutputStream(destination); - try { - fos.write(data); - } finally { - fos.close(); - } + @Override + public void transferTo (File destination) throws IOException { + val outputStream = new FileOutputStream(destination); + try { + outputStream.write(bytes); + } finally { + outputStream.close(); } - + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java index f8c82db32..a996228c5 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java @@ -1,80 +1,74 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.spring.converter; -import java.util.Collection; +import static lombok.AccessLevel.PRIVATE; + import java.util.HashMap; +import java.util.Locale; import java.util.Map; -import java.util.Set; + +import lombok.experimental.Delegate; +import lombok.experimental.FieldDefaults; /** * A Map implementation that normalizes the key to UPPER CASE, so * that value retrieval via the key is case insensitive. */ +@FieldDefaults(level = PRIVATE, makeFinal = true) final class IgnoreKeyCaseMap implements Map { - private final HashMap hashMap = new HashMap(); - - @Override - public int size() { - return hashMap.size(); - } - - @Override - public boolean isEmpty() { - return hashMap.isEmpty(); - } - - @Override - public boolean containsKey(final Object key) { - return hashMap.containsKey(normalizeKey(key)); - } - - @Override - public boolean containsValue(final Object value) { - return hashMap.containsValue(value); - } - - @Override - public String get(final Object key) { - return hashMap.get(normalizeKey(key)); - } - - @Override - public String put(final String key, final String value) { - return hashMap.put(normalizeKey(key), value); - } - - @Override - public String remove(final Object key) { - return hashMap.remove(normalizeKey(key)); - } - - @Override - public void putAll(final Map m) { - for (final Map.Entry entry : m.entrySet()) { - put(entry.getKey(), entry.getValue()); - } - } - - @Override - public void clear() { - hashMap.clear(); - } - - @Override - public Set keySet() { - return hashMap.keySet(); - } - - @Override - public Collection values() { - return hashMap.values(); - } - - @Override - public Set> entrySet() { - return hashMap.entrySet(); - } - - private static String normalizeKey(final Object key) { - return key != null ? key.toString().toUpperCase() : null; - } + + @Delegate(excludes = OverrideMap.class) + HashMap delegate = new HashMap(); + + @Override + public boolean containsKey (Object key) { + return delegate.containsKey(normalizeKey(key)); + } + + @Override + public String get (Object key) { + return delegate.get(normalizeKey(key)); + } + + @Override + public String put (String key, String value) { + return delegate.put(normalizeKey(key), value); + } + + @Override + public String remove (Object key) { + return delegate.remove(normalizeKey(key)); + } + + private static String normalizeKey (Object key) { + return key != null + ? key.toString().toUpperCase(new Locale("en_US")) + : null; + } + + private interface OverrideMap { + + boolean containsKey (Object key); + + String get (Object key); + + String put (String key, String value); + + String remove (Object key); + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index 4a68892c6..78de971b6 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -1,14 +1,35 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.spring.converter; +import static java.nio.charset.StandardCharsets.UTF_8; +import static lombok.AccessLevel.PRIVATE; +import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.LinkedList; -import java.util.List; import java.util.regex.Pattern; +import lombok.experimental.FieldDefaults; +import lombok.val; import org.apache.commons.fileupload.MultipartStream; - -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -22,118 +43,137 @@ /** * Implementation of {@link HttpMessageConverter} that can read multipart/form-data HTTP bodies * (writing is not handled because that is already supported by {@link FormHttpMessageConverter}). - * *

* This reader supports an array of {@link MultipartFile} as the mapping return class type - each * multipart body is read into an underlying byte array (in memory) implemented via * {@link ByteArrayMultipartFile}. */ +@FieldDefaults(level = PRIVATE, makeFinal = true) public class SpringManyMultipartFilesReader extends AbstractHttpMessageConverter { - private static final Pattern NEWLINES_PATTERN = Pattern.compile("\\R"); - private static final Pattern COLON_PATTERN = Pattern.compile(":"); - - private static final Pattern SEMICOLON_PATTERN = Pattern.compile(";"); - private static final Pattern EQUALITY_SIGN_PATTERN = Pattern.compile("="); - - private final int bufSize; - - /** - * Construct an {@code AbstractHttpMessageConverter} that can read mulitpart/form-data. - * @param bufSize The size of the buffer (in bytes) to read the HTTP multipart body. - */ - public SpringManyMultipartFilesReader(final int bufSize) { - super(MediaType.MULTIPART_FORM_DATA); - this.bufSize = bufSize; + private static final Pattern NEWLINES_PATTERN = Pattern.compile("\\R"); + + private static final Pattern COLON_PATTERN = Pattern.compile(":"); + + private static final Pattern SEMICOLON_PATTERN = Pattern.compile(";"); + + private static final Pattern EQUALITY_SIGN_PATTERN = Pattern.compile("="); + + int bufSize; + + /** + * Construct an {@code AbstractHttpMessageConverter} that can read mulitpart/form-data. + * + * @param bufSize The size of the buffer (in bytes) to read the HTTP multipart body. + */ + public SpringManyMultipartFilesReader (int bufSize) { + super(MULTIPART_FORM_DATA); + this.bufSize = bufSize; + } + + @Override + protected boolean canWrite (MediaType mediaType) { + return false; // Class NOT meant for writing multipart/form-data HTTP bodies + } + + @Override + protected boolean supports (Class clazz) { + return MultipartFile[].class == clazz; + } + + @Override + protected MultipartFile[] readInternal (Class clazz, HttpInputMessage inputMessage + ) throws IOException { + val boundaryBytes = getMultiPartBoundary(inputMessage.getHeaders().getContentType()); + MultipartStream multipartStream = new MultipartStream(inputMessage.getBody(), boundaryBytes, bufSize, null); + + val multiparts = new LinkedList(); + for (boolean nextPart = multipartStream.skipPreamble(); nextPart; nextPart = multipartStream.readBoundary()) { + ByteArrayMultipartFile multiPart; + try { + multiPart = readMultiPart(multipartStream); + } catch (Exception e) { + throw new HttpMessageNotReadableException("Multipart body could not be read.", e); + } + multiparts.add(multiPart); } - - @Override - protected boolean canWrite(final MediaType mediaType) { - return false; // Class NOT meant for writing multipart/form-data HTTP bodies - } - - @Override - protected boolean supports(final Class clazz) { - return MultipartFile[].class == clazz; + return multiparts.toArray(new ByteArrayMultipartFile[multiparts.size()]); + } + + @Override + protected void writeInternal (MultipartFile[] byteArrayMultipartFiles, HttpOutputMessage outputMessage) { + throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support writing to HTTP body."); + } + + private byte[] getMultiPartBoundary (MediaType contentType) { + val boundaryString = unquote(contentType.getParameter("boundary")); + if (!StringUtils.isEmpty(boundaryString)) { + return boundaryString.getBytes(UTF_8); + } else { + throw new HttpMessageNotReadableException("Content-Type missing boundary information."); } - - @Override - protected MultipartFile[] readInternal(final Class clazz, - final HttpInputMessage inputMessage) throws IOException { - final byte[] boundary = getMultiPartBoundary(inputMessage.getHeaders().getContentType()); - final MultipartStream multipartStream = new MultipartStream(inputMessage.getBody(), boundary, bufSize, null); - - final List multiparts = new LinkedList(); - for (boolean nextPart = multipartStream.skipPreamble(); nextPart; nextPart = multipartStream.readBoundary()) { - try { - multiparts.add(readMultiPart(multipartStream)); - } catch (final Exception e) { - throw new HttpMessageNotReadableException("Multipart body could not be read.", e); - } - } - - return multiparts.toArray(new ByteArrayMultipartFile[multiparts.size()]); - } - - @Override - protected void writeInternal(final MultipartFile[] byteArrayMultipartFiles, final HttpOutputMessage outputMessage) { - throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support writing to HTTP body."); - } - - private byte[] getMultiPartBoundary(final MediaType contentType) { - final String boundaryStr = unquote(contentType.getParameter("boundary")); - if (!StringUtils.isEmpty(boundaryStr)) { - return boundaryStr.getBytes(); - } else { - throw new HttpMessageNotReadableException("Content-Type missing boundary information."); - } - } - - private ByteArrayMultipartFile readMultiPart(final MultipartStream multipartStream) throws IOException { - final IgnoreKeyCaseMap multiPartHeaders = splitIntoKeyValuePairs(multipartStream.readHeaders(), - NEWLINES_PATTERN, COLON_PATTERN, false); - - final String multipartContentType = multiPartHeaders.get(HttpHeaders.CONTENT_TYPE); - final IgnoreKeyCaseMap contentDisposition = splitIntoKeyValuePairs( - multiPartHeaders.get(HttpHeaders.CONTENT_DISPOSITION), SEMICOLON_PATTERN, EQUALITY_SIGN_PATTERN, true); - - if (!contentDisposition.containsKey("form-data")) { - throw new HttpMessageNotReadableException("Content-Disposition is not of type form-data."); - } - - final ByteArrayOutputStream bodyStream = new ByteArrayOutputStream(); - multipartStream.readBodyData(bodyStream); - - return new ByteArrayMultipartFile(contentDisposition.get("name"), contentDisposition.get("filename"), - multipartContentType, bodyStream.toByteArray()); - } - - private IgnoreKeyCaseMap splitIntoKeyValuePairs(final String str, - final Pattern entriesSeparatorPattern, - final Pattern keyValueSeparatorPattern, - final boolean unquoteValue) { - final IgnoreKeyCaseMap keyValuePairs = new IgnoreKeyCaseMap(); - if (!StringUtils.isEmpty(str)) { - final String[] entries = entriesSeparatorPattern.split(str); - for (final String entry : entries) { - final String[] pair = keyValueSeparatorPattern.split(entry.trim(), 2); - final String key = pair[0].trim(); - final String value = pair.length > 1 ? pair[1].trim() : ""; - keyValuePairs.put(key, unquoteValue ? unquote(value) : value); - } - } - return keyValuePairs; + } + + private ByteArrayMultipartFile readMultiPart (MultipartStream multipartStream) throws IOException { + val multiPartHeaders = splitIntoKeyValuePairs( + multipartStream.readHeaders(), + NEWLINES_PATTERN, + COLON_PATTERN, + false + ); + + val contentDisposition = splitIntoKeyValuePairs( + multiPartHeaders.get(CONTENT_DISPOSITION), + SEMICOLON_PATTERN, + EQUALITY_SIGN_PATTERN, + true + ); + + if (!contentDisposition.containsKey("form-data")) { + throw new HttpMessageNotReadableException("Content-Disposition is not of type form-data."); } - private String unquote(final String value) { - return value != null - ? (isSurroundedBy(value, "\"") || isSurroundedBy(value, "'") - ? value.substring(1, value.length() - 1) - : value) - : null; + val bodyStream = new ByteArrayOutputStream(); + multipartStream.readBodyData(bodyStream); + return new ByteArrayMultipartFile( + contentDisposition.get("name"), + contentDisposition.get("filename"), + multiPartHeaders.get(CONTENT_TYPE), + bodyStream.toByteArray() + ); + } + + private IgnoreKeyCaseMap splitIntoKeyValuePairs (String str, Pattern entriesSeparatorPattern, + Pattern keyValueSeparatorPattern, boolean unquoteValue + ) { + val keyValuePairs = new IgnoreKeyCaseMap(); + if (!StringUtils.isEmpty(str)) { + val tokens = entriesSeparatorPattern.split(str); + for (val token : tokens) { + val pair = keyValueSeparatorPattern.split(token.trim(), 2); + val key = pair[0].trim(); + val value = pair.length > 1 + ? pair[1].trim() + : ""; + + keyValuePairs.put(key, unquoteValue + ? unquote(value) + : value); + } } + return keyValuePairs; + } - private boolean isSurroundedBy(final String value, final String preSuffix) { - return value.length() > 1 && value.startsWith(preSuffix) && value.endsWith(preSuffix); + private String unquote (String value) { + if (value == null) { + return null; } + return isSurroundedBy(value, "\"") || isSurroundedBy(value, "'") + ? value.substring(1, value.length() - 1) + : value; + } + + private boolean isSurroundedBy (String value, String preSuffix) { + return value.length() > 1 && value.startsWith(preSuffix) && value.endsWith(preSuffix); + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java index 690a45630..9a4536415 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java @@ -1,3 +1,4 @@ + package feign.form.feign.spring; import feign.codec.Decoder; @@ -14,10 +15,11 @@ import org.springframework.web.multipart.MultipartFile; import java.util.ArrayList; -import java.util.List; import static org.springframework.web.bind.annotation.RequestMethod.GET; +import lombok.val; + @FeignClient( name = "multipart-download-support-service", url = "http://localhost:8080", @@ -29,7 +31,7 @@ public interface DownloadClient { value = "/multipart/download/{fileId}", method = GET ) - MultipartFile[] download(@PathVariable("fileId") String fileId); + MultipartFile[] download (@PathVariable("fileId") String fileId); class ClientConfiguration { @@ -38,17 +40,18 @@ class ClientConfiguration { @Bean public Decoder feignDecoder () { - final List> springConverters = messageConverters.getObject().getConverters(); - final List> decoderConverters - = new ArrayList>(springConverters.size() + 1); + val springConverters = messageConverters.getObject().getConverters(); + val decoderConverters = new ArrayList>(springConverters.size() + 1); decoderConverters.addAll(springConverters); decoderConverters.add(new SpringManyMultipartFilesReader(4096)); - final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(decoderConverters); + + val httpMessageConverters = new HttpMessageConverters(decoderConverters); return new SpringDecoder(new ObjectFactory() { + @Override - public HttpMessageConverters getObject() { + public HttpMessageConverters getObject () { return httpMessageConverters; } }); diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index b73b9d67b..b635a8f9d 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -25,6 +25,7 @@ import java.util.Map; import lombok.SneakyThrows; +import lombok.val; import org.apache.commons.codec.CharEncoding; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.feign.EnableFeignClients; @@ -51,75 +52,76 @@ @SpringBootApplication public class Server { - @RequestMapping( - value = "/multipart/upload1/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE - ) - @SneakyThrows - public String upload1 (@PathVariable("folder") String folder, - @RequestPart MultipartFile file, - @RequestParam(value = "message", required = false) String message - ) { - return new String(file.getBytes()) + ':' + message + ':' + folder; - } + @RequestMapping( + value = "/multipart/upload1/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + @SneakyThrows + public String upload1 (@PathVariable("folder") String folder, + @RequestPart MultipartFile file, + @RequestParam(value = "message", required = false) String message + ) { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } - @RequestMapping( - value = "/multipart/upload2/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE - ) - @SneakyThrows - public String upload2 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message - ) { - return new String(file.getBytes()) + ':' + message + ':' + folder; - } + @RequestMapping( + value = "/multipart/upload2/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + @SneakyThrows + public String upload2 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message + ) { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } - @RequestMapping( - value = "/multipart/upload3/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE - ) - public String upload3 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message - ) { - return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; - } + @RequestMapping( + value = "/multipart/upload3/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + public String upload3 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message + ) { + return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; + } - @RequestMapping( - path = "/multipart/upload4/{id}", - method = POST - ) - public String upload4 (@PathVariable("id") String id, - @RequestBody Map map, - @RequestParam String userName - ) { - return userName + ':' + id + ':' + map.size(); - } + @RequestMapping( + path = "/multipart/upload4/{id}", + method = POST + ) + public String upload4 (@PathVariable("id") String id, + @RequestBody Map map, + @RequestParam String userName + ) { + return userName + ':' + id + ':' + map.size(); + } - @RequestMapping( - value = "/multipart/download/{fileId}", - method = GET, - produces = MULTIPART_FORM_DATA_VALUE - ) - public MultiValueMap download(@PathVariable("fileId") String fileId) { - MultiValueMap multiParts = new LinkedMultiValueMap(); + @RequestMapping( + value = "/multipart/download/{fileId}", + method = GET, + produces = MULTIPART_FORM_DATA_VALUE + ) + public MultiValueMap download (@PathVariable("fileId") String fileId) { + val multiParts = new LinkedMultiValueMap(); - String info = "The text for file ID " + fileId + ". Testing unicode €"; - HttpHeaders infoPartheader = new HttpHeaders(); - infoPartheader.setContentType(new MediaType("text", "plain", Charset.forName(CharEncoding.UTF_8))); - HttpEntity infoPart = new HttpEntity(info, infoPartheader); + val infoString = "The text for file ID " + fileId + ". Testing unicode €"; + val infoPartheader = new HttpHeaders(); + infoPartheader.setContentType(new MediaType("text", "plain", Charset.forName(CharEncoding.UTF_8))); - ClassPathResource file = new ClassPathResource("testfile.txt"); - HttpHeaders filePartheader = new HttpHeaders(); - filePartheader.setContentType(APPLICATION_OCTET_STREAM); - HttpEntity filePart = new HttpEntity(file, filePartheader); + val infoPart = new HttpEntity(infoString, infoPartheader); - multiParts.add("info", infoPart); - multiParts.add("file", filePart); - return multiParts; - } + val file = new ClassPathResource("testfile.txt"); + val filePartheader = new HttpHeaders(); + filePartheader.setContentType(APPLICATION_OCTET_STREAM); + val filePart = new HttpEntity(file, filePartheader); + + multiParts.add("info", infoPart); + multiParts.add("file", filePart); + return multiParts; + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java index 52d994665..2fef8e3b6 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java @@ -41,7 +41,7 @@ public void downloadTest() throws Exception { Assert.assertEquals("testfile.txt", downloads[1].getOriginalFilename()); Assert.assertEquals(MediaType.APPLICATION_OCTET_STREAM_VALUE, downloads[1].getContentType()); - Assert.assertEquals(13, downloads[1].getSize()); + Assert.assertEquals(14, downloads[1].getSize()); } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java index 81d36586f..77061f18e 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java @@ -1,5 +1,10 @@ + package feign.form.feign.spring.converter; +import static java.util.Collections.singletonList; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + import feign.form.spring.converter.SpringManyMultipartFilesReader; import org.apache.commons.io.IOUtils; import org.junit.Assert; @@ -13,16 +18,16 @@ import java.io.IOException; import java.io.InputStream; -import static java.util.Collections.singletonList; +import lombok.val; public class SpringManyMultipartFilesReaderTest { - private static final String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; + private final static String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; @Test - public void readMultipartFormDataTest() throws IOException { - final SpringManyMultipartFilesReader multipartFilesReader = new SpringManyMultipartFilesReader(4096); - final MultipartFile[] multipartFiles = multipartFilesReader.read(MultipartFile[].class, new ValidMulitpartMessage()); + public void readMultipartFormDataTest () throws IOException { + val multipartFilesReader = new SpringManyMultipartFilesReader(4096); + val multipartFiles = multipartFilesReader.read(MultipartFile[].class, new ValidMultipartMessage()); Assert.assertEquals(2, multipartFiles.length); @@ -35,29 +40,32 @@ public void readMultipartFormDataTest() throws IOException { Assert.assertEquals("Plain text", IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")); } - public static class ValidMulitpartMessage implements HttpInputMessage { + public static class ValidMultipartMessage implements HttpInputMessage { + @Override - public InputStream getBody() throws IOException { - final String multipartBody = "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + - "Content-Type: application/json\r\n" + - "Content-Disposition: form-data; name=\"form-item-1\"\r\n" + - "\r\n" + - "{\"id\":1}" + "\r\n" + - "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + - "content-type: text/plain\r\n" + - "content-disposition: Form-Data; Filename=\"form-item-2-file-1\"; Name=\"form-item-2\"\r\n" + - "\r\n" + - "Plain text" + "\r\n" + - "--" + DUMMY_MULTIPART_BOUNDARY + "--\r\n"; + public InputStream getBody () throws IOException { + val multipartBody = "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + + "Content-Type: application/json\r\n" + + "Content-Disposition: form-data; name=\"form-item-1\"\r\n" + + "\r\n" + + "{\"id\":1}" + "\r\n" + + "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + + "content-type: text/plain\r\n" + + "content-disposition: Form-Data; Filename=\"form-item-2-file-1\"; Name=\"form-item-2\"\r\n" + + "\r\n" + + "Plain text" + "\r\n" + + "--" + DUMMY_MULTIPART_BOUNDARY + "--\r\n"; return new ByteArrayInputStream(multipartBody.getBytes("US-ASCII")); } @Override - public HttpHeaders getHeaders() { - final HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put(HttpHeaders.CONTENT_TYPE, - singletonList(MediaType.MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY)); + public HttpHeaders getHeaders () { + val httpHeaders = new HttpHeaders(); + httpHeaders.put( + CONTENT_TYPE, + singletonList(MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY) + ); return httpHeaders; } } diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 1123e9e7d..7b66508f9 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.1.0 + 3.2.0 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index cc3a9eeb9..8ef206e1e 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.1.0 + 3.2.0 pom @@ -69,7 +69,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.1.0 + 3.2.0 From da2c1a6f8dc25534b79b24186eff9593617e396b Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 16 Jan 2018 14:23:02 +0300 Subject: [PATCH 47/93] Checkstyle fix and few minor style fixes --- .codestyle/checkstyle.xml | 8 -- README.md | 8 +- feign-form-spring/pom.xml | 2 +- .../SpringManyMultipartFilesWriter.java | 4 +- .../feign/spring/SpringFormEncoderTest.java | 2 +- feign-form/pom.xml | 2 +- .../feign/form/multipart/ManyFilesWriter.java | 4 +- .../test/java/feign/form/BasicClientTest.java | 5 +- .../test/java/feign/form/WildCardMapTest.java | 83 +++++++++++-------- pom.xml | 12 ++- 10 files changed, 74 insertions(+), 56 deletions(-) diff --git a/.codestyle/checkstyle.xml b/.codestyle/checkstyle.xml index e06be59d7..6c392af3f 100644 --- a/.codestyle/checkstyle.xml +++ b/.codestyle/checkstyle.xml @@ -33,14 +33,6 @@ limitations under the License. - - - - - - - - diff --git a/README.md b/README.md index 011307427..64deb7819 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,14 @@ This module adds support for encoding **application/x-www-form-urlencoded** and ## Add dependency Include the dependency to your project's pom.xml file: + ```xml ... io.github.openfeign.form feign-form - 3.2.0 + 3.2.1 ... @@ -94,18 +95,19 @@ In example above, we send file in parameter named **photo** with additional fiel You can also use Form Encoder with Spring `MultipartFile` and `@FeignClient`. Include the dependencies to your project's pom.xml file: + ```xml ... io.github.openfeign.form feign-form - 3.2.0 + 3.2.1 io.github.openfeign.form feign-form-spring - 3.2.0 + 3.2.1 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 950243782..c555498ba 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.0 + 3.2.1 Open Feign Forms Extension for Spring diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java index 335d2f6e7..ae1d9acf4 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java @@ -42,7 +42,7 @@ public void write (Output output, String boundary, String key, Object value) thr fileWriter.write(output, boundary, key, file); } } else if (value instanceof Iterable) { - val iterable = (Iterable) value; + val iterable = (Iterable) value; for (val file : iterable) { fileWriter.write(output, boundary, key, file); } @@ -58,7 +58,7 @@ public boolean isApplicable (Object value) { return true; } if (value instanceof Iterable) { - val iterable = (Iterable) value; + val iterable = (Iterable) value; val iterator = iterable.iterator(); if (iterator.hasNext() && iterator.next() instanceof MultipartFile) { return true; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 2ee5dd245..9f4c4e362 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -87,7 +87,7 @@ public void uploadFileNameAndContentTypeTest () throws Exception { @Test public void upload4Test () throws Exception { - val map = new HashMap<>(); + val map = new HashMap(); map.put("one", 1); map.put("two", 2); diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 7b66508f9..1e88108f6 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.0 + 3.2.1 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java index 542acc5a2..029cd4fd6 100644 --- a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java @@ -40,7 +40,7 @@ public void write (Output output, String boundary, String key, Object value) thr fileWriter.write(output, boundary, key, file); } } else if (value instanceof Iterable) { - val iterable = (Iterable) value; + val iterable = (Iterable) value; for (val file : iterable) { fileWriter.write(output, boundary, key, file); } @@ -56,7 +56,7 @@ public boolean isApplicable (Object value) { return true; } if (value instanceof Iterable) { - val iterable = (Iterable) value; + val iterable = (Iterable) value; val iterator = iterable.iterator(); if (iterator.hasNext() && iterator.next() instanceof File) { return true; diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index 7ae127c80..142a7446f 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -16,6 +16,7 @@ package feign.form; +import static java.util.Collections.singletonMap; import static feign.Logger.Level.FULL; import static java.util.Arrays.asList; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; @@ -25,8 +26,6 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; import java.util.Map; import lombok.val; import org.junit.Assert; @@ -100,7 +99,7 @@ public void testJson () { @Test public void testQueryMap () { - Map value = Collections.singletonMap("filter", Arrays.asList("one", "two", "three", "four")); + Map value = singletonMap("filter", (Object) asList("one", "two", "three", "four")); val stringResponse = api.queryMap(value); Assert.assertEquals("4", stringResponse); diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index 8332ea5d4..88749ac09 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -32,40 +32,57 @@ @RunWith(SpringRunner.class) @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class + webEnvironment = DEFINED_PORT, + classes = Server.class ) public class WildCardMapTest { - private static FormUrlEncodedApi API; - - @BeforeClass - public static void configureClient() { - API = Feign.builder() - .encoder(new FormEncoder()) - .logger(new Logger.JavaLogger().appendToFile("log.txt")) - .logLevel(FULL) - .target(FormUrlEncodedApi.class, "http://localhost:8080"); - } - - @Test - public void testOk() { - Map param = new HashMap() {{put("key1", "1"); put("key2", "1");}}; - Response response = API.wildCardMap(param); - Assert.assertEquals(200, response.status()); - } - - @Test - public void testBadRequest() { - Map param = new HashMap() {{put("key1", "1"); put("key2", "2");}}; - Response response = API.wildCardMap(param); - Assert.assertEquals(418, response.status()); - } - - interface FormUrlEncodedApi { - - @RequestLine("POST /wild-card-map") - @Headers("Content-Type: application/x-www-form-urlencoded") - Response wildCardMap(Map param); - } + private static FormUrlEncodedApi API; + + @BeforeClass + public static void configureClient () { + API = Feign.builder() + .encoder(new FormEncoder()) + .logger(new Logger.JavaLogger().appendToFile("log.txt")) + .logLevel(FULL) + .target(FormUrlEncodedApi.class, "http://localhost:8080"); + } + + @Test + public void testOk () { + Map param = new HashMap() { + + private static final long serialVersionUID = 3109256773218160485L; + + { + put("key1", "1"); + put("key2", "1"); + } + }; + Response response = API.wildCardMap(param); + Assert.assertEquals(200, response.status()); + } + + @Test + public void testBadRequest () { + Map param = new HashMap() { + + private static final long serialVersionUID = 3109256773218160485L; + + { + + put("key1", "1"); + put("key2", "2"); + } + }; + Response response = API.wildCardMap(param); + Assert.assertEquals(418, response.status()); + } + + interface FormUrlEncodedApi { + + @RequestLine("POST /wild-card-map") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response wildCardMap (Map param); + } } diff --git a/pom.xml b/pom.xml index 8ef206e1e..514b586ef 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.0 + 3.2.1 pom @@ -69,7 +69,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.2.0 + 3.2.1 @@ -207,6 +207,14 @@ limitations under the License. 8.7 + + + + + + + + validate From ea35bd6391718154f0a13d85dbe1a9e25c267eec Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 16 Jan 2018 15:09:26 +0300 Subject: [PATCH 48/93] Animal sniffer fixes --- README.md | 6 +-- feign-form-spring/pom.xml | 2 +- .../spring/converter/IgnoreKeyCaseMap.java | 43 ++++++------------- .../SpringManyMultipartFilesReader.java | 15 ++++--- .../java/feign/form/feign/spring/Server.java | 5 +-- .../feign/spring/SpringFormEncoderTest.java | 2 +- feign-form/pom.xml | 2 +- .../src/main/java/feign/form/FormEncoder.java | 6 +-- .../java/feign/form/util/CharsetUtil.java | 31 +++++++++++++ pom.xml | 4 +- 10 files changed, 63 insertions(+), 53 deletions(-) create mode 100644 feign-form/src/main/java/feign/form/util/CharsetUtil.java diff --git a/README.md b/README.md index 64deb7819..d5ea4cb2a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.2.1 + 3.2.2 ... @@ -102,12 +102,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.2.1 + 3.2.2 io.github.openfeign.form feign-form-spring - 3.2.1 + 3.2.2 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index c555498ba..161ce10a9 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.1 + 3.2.2 Open Feign Forms Extension for Spring diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java index a996228c5..10e5cb4ee 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java @@ -16,59 +16,40 @@ package feign.form.spring.converter; -import static lombok.AccessLevel.PRIVATE; - import java.util.HashMap; import java.util.Locale; -import java.util.Map; - -import lombok.experimental.Delegate; -import lombok.experimental.FieldDefaults; /** * A Map implementation that normalizes the key to UPPER CASE, so * that value retrieval via the key is case insensitive. */ -@FieldDefaults(level = PRIVATE, makeFinal = true) -final class IgnoreKeyCaseMap implements Map { +final class IgnoreKeyCaseMap extends HashMap { + + private static final long serialVersionUID = -2321516556941546746L; - @Delegate(excludes = OverrideMap.class) - HashMap delegate = new HashMap(); + private static String normalizeKey (Object key) { + return key != null + ? key.toString().toUpperCase(new Locale("en_US")) + : null; + } @Override public boolean containsKey (Object key) { - return delegate.containsKey(normalizeKey(key)); + return super.containsKey(normalizeKey(key)); } @Override public String get (Object key) { - return delegate.get(normalizeKey(key)); + return super.get(normalizeKey(key)); } @Override public String put (String key, String value) { - return delegate.put(normalizeKey(key), value); + return super.put(normalizeKey(key), value); } @Override public String remove (Object key) { - return delegate.remove(normalizeKey(key)); - } - - private static String normalizeKey (Object key) { - return key != null - ? key.toString().toUpperCase(new Locale("en_US")) - : null; - } - - private interface OverrideMap { - - boolean containsKey (Object key); - - String get (Object key); - - String put (String key, String value); - - String remove (Object key); + return super.remove(normalizeKey(key)); } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index 78de971b6..c3631155b 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -16,7 +16,7 @@ package feign.form.spring.converter; -import static java.nio.charset.StandardCharsets.UTF_8; +import static feign.form.util.CharsetUtil.UTF_8; import static lombok.AccessLevel.PRIVATE; import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; @@ -25,6 +25,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.LinkedList; +import java.util.Map; import java.util.regex.Pattern; import lombok.experimental.FieldDefaults; @@ -143,8 +144,8 @@ private ByteArrayMultipartFile readMultiPart (MultipartStream multipartStream) t ); } - private IgnoreKeyCaseMap splitIntoKeyValuePairs (String str, Pattern entriesSeparatorPattern, - Pattern keyValueSeparatorPattern, boolean unquoteValue + private Map splitIntoKeyValuePairs (String str, Pattern entriesSeparatorPattern, + Pattern keyValueSeparatorPattern, boolean unquoteValue ) { val keyValuePairs = new IgnoreKeyCaseMap(); if (!StringUtils.isEmpty(str)) { @@ -153,12 +154,12 @@ private IgnoreKeyCaseMap splitIntoKeyValuePairs (String str, Pattern entriesSepa val pair = keyValueSeparatorPattern.split(token.trim(), 2); val key = pair[0].trim(); val value = pair.length > 1 - ? pair[1].trim() - : ""; + ? pair[1].trim() + : ""; keyValuePairs.put(key, unquoteValue - ? unquote(value) - : value); + ? unquote(value) + : value); } } return keyValuePairs; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index b635a8f9d..5033d5277 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -20,13 +20,12 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static feign.form.util.CharsetUtil.UTF_8; -import java.nio.charset.Charset; import java.util.Map; import lombok.SneakyThrows; import lombok.val; -import org.apache.commons.codec.CharEncoding; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.core.io.ClassPathResource; @@ -111,7 +110,7 @@ public MultiValueMap download (@PathVariable("fileId") String fi val infoString = "The text for file ID " + fileId + ". Testing unicode €"; val infoPartheader = new HttpHeaders(); - infoPartheader.setContentType(new MediaType("text", "plain", Charset.forName(CharEncoding.UTF_8))); + infoPartheader.setContentType(new MediaType("text", "plain", UTF_8)); val infoPart = new HttpEntity(infoString, infoPartheader); diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 9f4c4e362..685ebc4df 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -16,8 +16,8 @@ package feign.form.feign.spring; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static feign.form.util.CharsetUtil.UTF_8; import java.util.HashMap; import lombok.val; diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 1e88108f6..9cebe2738 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.1 + 3.2.2 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index 117ffd3d1..22a228dc8 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -16,6 +16,7 @@ package feign.form; +import static feign.form.util.CharsetUtil.UTF_8; import static java.util.Arrays.asList; import static lombok.AccessLevel.PRIVATE; @@ -44,12 +45,9 @@ public class FormEncoder implements Encoder { private static final Pattern CHARSET_PATTERN; - private static final Charset DEFAULT_CHARSET; - static { CONTENT_TYPE_HEADER = "Content-Type"; CHARSET_PATTERN = Pattern.compile("(?<=charset=)([\\w\\-]+)"); - DEFAULT_CHARSET = Charset.forName("UTF-8"); } Encoder delegate; @@ -130,6 +128,6 @@ private Charset getCharset (String contentTypeValue) { val matcher = CHARSET_PATTERN.matcher(contentTypeValue); return matcher.find() ? Charset.forName(matcher.group(1)) - : DEFAULT_CHARSET; + : UTF_8; } } diff --git a/feign-form/src/main/java/feign/form/util/CharsetUtil.java b/feign-form/src/main/java/feign/form/util/CharsetUtil.java new file mode 100644 index 000000000..ba6979a29 --- /dev/null +++ b/feign-form/src/main/java/feign/form/util/CharsetUtil.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.util; + +import java.nio.charset.Charset; + +/** + * + * @author Artem Labazin + */ +public final class CharsetUtil { + + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + private CharsetUtil () { + } +} diff --git a/pom.xml b/pom.xml index 514b586ef..1d5ae4c2c 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.1 + 3.2.2 pom @@ -69,7 +69,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.2.1 + 3.2.2 From 94422bf0c4d80e5c7ed2bd0b1dd76faa4f03228a Mon Sep 17 00:00:00 2001 From: SilverFox Date: Fri, 19 Jan 2018 22:47:43 +0800 Subject: [PATCH 49/93] ContentType fallback to application/octet-stream when guess failed --- .../java/feign/form/multipart/AbstractWriter.java | 9 ++++++--- .../src/test/java/feign/form/BasicClientTest.java | 9 +++++++++ feign-form/src/test/java/feign/form/Server.java | 11 +++++++++++ feign-form/src/test/java/feign/form/TestClient.java | 4 ++++ feign-form/src/test/resources/file.abc | 0 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 feign-form/src/test/resources/file.abc diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index 319f5abe4..cba28954c 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -66,9 +66,12 @@ protected void writeFileMetadata (Output output, String name, String fileName, S String fileContentType = contentType; if (fileContentType == null) { - fileContentType = fileName != null - ? URLConnection.guessContentTypeFromName(fileName) - : "application/octet-stream"; + if (fileName != null) { + fileContentType = URLConnection.guessContentTypeFromName(fileName); + } + if (fileContentType == null) { + fileContentType = "application/octet-stream"; + } } val string = new StringBuilder() diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index 142a7446f..d78c1b123 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -149,4 +149,13 @@ public void testUploadWithJson () throws Exception { Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); } + + @Test + public void testUnknownTypeFile() throws Exception { + val path = Paths.get(this.getClass().getClassLoader().getResource("file.abc").toURI()); + Assert.assertTrue(Files.exists(path)); + + val stringResponse = api.uploadUnknownType(path.toFile()); + Assert.assertEquals("application/octet-stream", stringResponse); + } } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index fddf91db0..ad924aafb 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -167,4 +167,15 @@ public ResponseEntity uploadByteArray (@RequestPart("file") MultipartFil : I_AM_A_TEAPOT; return ResponseEntity.status(status).body(file.getOriginalFilename()); } + + @PostMapping( + path = "/upload/unknown_type", + consumes = MULTIPART_FORM_DATA_VALUE + ) + public ResponseEntity uploadUnknownType (@RequestPart("file") MultipartFile file) { + val status = file != null + ? OK + : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(file.getContentType()); + } } diff --git a/feign-form/src/test/java/feign/form/TestClient.java b/feign-form/src/test/java/feign/form/TestClient.java index 62f276f8a..d12b73df9 100644 --- a/feign-form/src/test/java/feign/form/TestClient.java +++ b/feign-form/src/test/java/feign/form/TestClient.java @@ -65,4 +65,8 @@ public interface TestClient { @RequestLine("POST /upload/with_json") @Headers("Content-Type: multipart/form-data") Response uploadWithJson (@Param("dto") Dto dto, @Param("file") File file); + + @RequestLine("POST /upload/unknown_type") + @Headers("Content-Type: multipart/form-data") + String uploadUnknownType (@Param("file") File file); } diff --git a/feign-form/src/test/resources/file.abc b/feign-form/src/test/resources/file.abc new file mode 100644 index 000000000..e69de29bb From 4827e726af645b8e13f2cc7aff8565b6fbd9e4e0 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Fri, 19 Jan 2018 23:08:09 +0800 Subject: [PATCH 50/93] Checkstyle fix --- .../src/main/java/feign/form/multipart/AbstractWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index cba28954c..17e12e61e 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -67,10 +67,10 @@ protected void writeFileMetadata (Output output, String name, String fileName, S String fileContentType = contentType; if (fileContentType == null) { if (fileName != null) { - fileContentType = URLConnection.guessContentTypeFromName(fileName); + fileContentType = URLConnection.guessContentTypeFromName(fileName); } if (fileContentType == null) { - fileContentType = "application/octet-stream"; + fileContentType = "application/octet-stream"; } } From b83e54b29d52776e457444afcd4c85ecec8a650d Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sat, 20 Jan 2018 00:23:52 +0300 Subject: [PATCH 51/93] Travis usage refactoring --- .codestyle/checkstyle.xml | 7 ++ .codestyle/license_for_check.txt | 15 +++ .gitignore | 2 +- .settings.xml | 24 ++-- .travis.yml | 53 ++++---- .travis/publish.sh | 118 ------------------ README.md | 3 + .../src/test/resources/testfile.txt | 1 + .../src/test/resources/another_file.txt | 2 + pom.xml | 77 +++++++----- 10 files changed, 115 insertions(+), 187 deletions(-) create mode 100644 .codestyle/license_for_check.txt delete mode 100755 .travis/publish.sh create mode 100644 feign-form-spring/src/test/resources/testfile.txt create mode 100644 feign-form/src/test/resources/another_file.txt diff --git a/.codestyle/checkstyle.xml b/.codestyle/checkstyle.xml index 6c392af3f..be7c471a6 100644 --- a/.codestyle/checkstyle.xml +++ b/.codestyle/checkstyle.xml @@ -22,6 +22,13 @@ limitations under the License. + + + + + + + diff --git a/.codestyle/license_for_check.txt b/.codestyle/license_for_check.txt new file mode 100644 index 000000000..f4f231534 --- /dev/null +++ b/.codestyle/license_for_check.txt @@ -0,0 +1,15 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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. + */ diff --git a/.gitignore b/.gitignore index e6f45c702..c705eab37 100644 --- a/.gitignore +++ b/.gitignore @@ -109,6 +109,6 @@ nb-configuration.xml .nb-gradle/ # Test output log file -*.txt* +log*.txt* .DS_Store .vscode/ diff --git a/.settings.xml b/.settings.xml index 13da65702..797ed3c9f 100644 --- a/.settings.xml +++ b/.settings.xml @@ -4,19 +4,21 @@ http://maven.apache.org/xsd/settings-1.0.0.xsd"> - sonatype + ossrh ${env.SONATYPE_USER} ${env.SONATYPE_PASSWORD} - - bintray - ${env.BINTRAY_USER} - ${env.BINTRAY_KEY} - - - github.com - ${env.GH_USER} - ${env.GH_TOKEN} - + + + ossrh + + true + + + ${env.GPG_EXECUTABLE} + ${env.GPG_PASSPHRASE} + + + diff --git a/.travis.yml b/.travis.yml index 615950dea..3fb9b9c24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,36 +1,43 @@ -# Run `travis lint` when changing this file to avoid breaking the build. -# Default JDK is really old: 1.8.0_31; Trusty's is less old: 1.8.0_51 -# https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments -sudo: required -dist: trusty + +language: java cache: directories: - $HOME/.m2 -language: java - jdk: - oraclejdk8 - before_install: - # Parameters used during release - - git config user.name "$GH_USER" - - git config user.email "$GH_USER_EMAIL" - # setup https authentication credentials, used by ./mvnw release:prepare - - git config credential.helper "store --file=.git/credentials" - - echo "https://$GH_TOKEN:@github.com" > .git/credentials + - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import + - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust install: - # Override default travis to use the maven wrapper - - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V + mvn --settings .settings.xml clean install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V script: - - ./.travis/publish.sh - -# Don't build release tags. This avoids publish conflicts because the version commit exists both on master and the release tag. -# See https://github.com/travis-ci/travis-ci/issues/1532 -branches: - except: - - /^[0-9]/ \ No newline at end of file + mvn --settings .settings.xml verify -DskipTests=false -Dmaven.javadoc.skip=false -B -U + +before_deploy: + - mvn help:evaluate -N -Dexpression=project.version | grep -v '\[' + - export project_version=$(mvn help:evaluate -N -Dexpression=project.version | grep -v '\[') + +deploy: + provider: releases + api_key: + secure: D6QIU6EyLGlsZJMmnq/xK+GgjaeyVsXtt42pZMmxN5/CDJE83am6kAgF8TmM3ZLT8PGteRq/U+BYXMFipagXn4RDvFWRq+ZjjCPzXZ0+CLO7EbzTNgtIzweMYrx6EwnMyQ6TDP7M/hjylB5LePsi6ASP3GLbKQ9GuaDPCnLJGGDzuRJPbCuk0TIKpfEz+WxC7vL/FqJluPs3wINR1dv4rTGjz9I/unxBwiSkUZHgI08jbJOPr19mFUej2L5+hm40H9THKGqEwTCDEjIiJodv6gIAvK8Kt+upRewNdX8AJiLnK877KLnhm8gsZy14gVx8fZ3/xEC4EB44z4UQjfZL0FBtw2cwFe/zij0q8v53h0vjEpJEZeIqwW4QrbADk7Sr5EqCCVdq+S/lLxuEPgnAw7ARX87fELrIP3DyONVP773sC2k3ZUi9MZjBmlwSgHB2Si30deBSxIVKgWCRdGyM2hMAU2nSfM4tuZMMivucrh3+ibppJxNpl512UBUkXNX16/mjeQlv3Ve3FZ08C+GZ+1A+t7orPok9mz7k0h2FPx39YmvDc1pWbKBtPAbgFj+Uyebuc15wf5JDDTDgmr3oBGHjWMj6PaA+ovJ0lti4g5QzboP9rI3HC7Jz44KIlX55eUaRhcWBxZJWxIAfPpJ1cKKrztjMhPjPh147epLbXX4= + file: + - "feign-form/target/feign-form-$project_version.jar" + - "feign-form/target/feign-form-$project_version-javadoc.jar" + - "feign-form/target/feign-form-$project_version-sources.jar" + - "feign-form-spring/target/feign-form-spring-$project_version.jar" + - "feign-form-spring/target/feign-form-spring-$project_version-javadoc.jar" + - "feign-form-spring/target/feign-form-spring-$project_version-sources.jar" + skip_cleanup: true + on: + tags: true + repo: OpenFeign/feign-form + name: $project_version + +after_deploy: + mvn --settings .settings.xml deploy -DskipTests=true -Dmaven.javadoc.skip=true -B -U diff --git a/.travis/publish.sh b/.travis/publish.sh deleted file mode 100755 index a2235b4b6..000000000 --- a/.travis/publish.sh +++ /dev/null @@ -1,118 +0,0 @@ -# taken from OpenZipkin - -set -euo pipefail -set -x - -build_started_by_tag() { - if [ "${TRAVIS_TAG}" == "" ]; then - echo "[Publishing] This build was not started by a tag, publishing snapshot" - return 1 - else - echo "[Publishing] This build was started by the tag ${TRAVIS_TAG}, publishing release" - return 0 - fi -} - -is_pull_request() { - if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then - echo "[Not Publishing] This is a Pull Request" - return 0 - else - echo "[Publishing] This is not a Pull Request" - return 1 - fi -} - -is_travis_branch_master() { - if [ "${TRAVIS_BRANCH}" = master ]; then - echo "[Publishing] Travis branch is master" - return 0 - else - echo "[Not Publishing] Travis branch is not master" - return 1 - fi -} - -check_travis_branch_equals_travis_tag() { - #Weird comparison comparing branch to tag because when you 'git push --tags' - #the branch somehow becomes the tag value - #github issue: https://github.com/travis-ci/travis-ci/issues/1675 - if [ "${TRAVIS_BRANCH}" != "${TRAVIS_TAG}" ]; then - echo "Travis branch does not equal Travis tag, which it should, bailing out." - echo " github issue: https://github.com/travis-ci/travis-ci/issues/1675" - exit 1 - else - echo "[Publishing] Branch (${TRAVIS_BRANCH}) same as Tag (${TRAVIS_TAG})" - fi -} - -check_release_tag() { - tag="${TRAVIS_TAG}" - if [[ "$tag" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then - echo "Build started by version tag $tag. During the release process tags like this" - echo "are created by the 'release' Maven plugin. Nothing to do here." - exit 0 - elif [[ ! "$tag" =~ ^release-[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then - echo "You must specify a tag of the format 'release-0.0.0' to release this project." - echo "The provided tag ${tag} doesn't match that. Aborting." - exit 1 - fi -} - -is_release_commit() { - project_version=$(mvn help:evaluate -N -Dexpression=project.version|grep -v '\[') - if [[ "$project_version" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then - echo "Build started by release commit $project_version. Will synchronize to maven central." - return 0 - else - return 1 - fi -} - -release_version() { - echo "${TRAVIS_TAG}" | sed 's/^release-//' -} - -safe_checkout_master() { - # We need to be on a branch for release:perform to be able to create commits, and we want that branch to be master. - # But we also want to make sure that we build and release exactly the tagged version, so we verify that the remote - # master is where our tag is. - git checkout -B master - git fetch origin master:origin/master - commit_local_master="$(git show --pretty='format:%H' master)" - commit_remote_master="$(git show --pretty='format:%H' origin/master)" - if [ "$commit_local_master" != "$commit_remote_master" ]; then - echo "Master on remote 'origin' has commits since the version under release, aborting" - exit 1 - fi -} - -#---------------------- -# MAIN -#---------------------- - -if ! is_pull_request && build_started_by_tag; then - check_travis_branch_equals_travis_tag - check_release_tag -fi - -mvn install -nsu -DskipTests - -# If we are on a pull request, our only job is to run tests, which happened above via mvn install -if is_pull_request; then - true -# If we are on master, we will deploy the latest snapshot or release version -# - If a release commit fails to deploy for a transient reason, delete the broken version from bintray and click rebuild -elif is_travis_branch_master; then - mvn --batch-mode -s ./.settings.xml -Prelease -nsu -DskipTests deploy - - # If the deployment succeeded, sync it to Maven Central. Note: this needs to be done once per project, not module, hence -N - if is_release_commit; then - mvn --batch-mode -s ./.settings.xml -nsu -N io.zipkin.centralsync-maven-plugin:centralsync-maven-plugin:sync - fi - -# If we are on a release tag, the following will update any version references and push a version tag for deployment. -elif build_started_by_tag; then - safe_checkout_master - mvn --batch-mode -s ./.settings.xml -Prelease -nsu -DreleaseVersion="$(release_version)" -Darguments="-DskipTests" release:prepare -fi diff --git a/README.md b/README.md index d5ea4cb2a..fc86e8e62 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Form Encoder +[![build_status](https://travis-ci.org/OpenFeign/feign-form.svg?branch=master)](https://travis-ci.org/OpenFeign/feign-form) +[![maven_central](https://maven-badges.herokuapp.com/maven-central/io.github.openfeign.form/feign-form/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.openfeign.form/feign-form) + This module adds support for encoding **application/x-www-form-urlencoded** and **multipart/form-data** forms. ## Add dependency diff --git a/feign-form-spring/src/test/resources/testfile.txt b/feign-form-spring/src/test/resources/testfile.txt new file mode 100644 index 000000000..c8fc1be13 --- /dev/null +++ b/feign-form-spring/src/test/resources/testfile.txt @@ -0,0 +1 @@ +My test text. diff --git a/feign-form/src/test/resources/another_file.txt b/feign-form/src/test/resources/another_file.txt new file mode 100644 index 000000000..ca9df1127 --- /dev/null +++ b/feign-form/src/test/resources/another_file.txt @@ -0,0 +1,2 @@ + +Another hello! diff --git a/pom.xml b/pom.xml index 1d5ae4c2c..191453e46 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,27 @@ limitations under the License. 3.2.2 + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + GitHub Issues + https://github.com/OpenFeign/feign-form/issues + + + + Travis + https://travis-ci.org/infobip/popout + + Artem Labazin @@ -83,18 +104,6 @@ limitations under the License. - - - bintray - https://api.bintray.com/maven/openfeign/maven/feign-form/;publish=1 - - - - - Github - https://github.com/OpenFeign/feign-form/issues - - @@ -207,14 +216,6 @@ limitations under the License. 8.7 - - - - - - - - validate @@ -228,6 +229,7 @@ limitations under the License. true true false + codestyleFolder=${project.basedir}/../.codestyle @@ -317,26 +319,33 @@ limitations under the License. + - maven-release-plugin - 2.5.3 + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true - false - release - true - @{project.version} + ossrh + https://oss.sonatype.org/ + true + - io.zipkin.centralsync-maven-plugin - centralsync-maven-plugin - 0.1.0 - - openfeign - maven - feign-form - + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + From 22dafeb19fedaf0e8ba0c037133424df8bf028b0 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Wed, 24 Jan 2018 02:23:16 +0300 Subject: [PATCH 52/93] Correct Travis file --- .travis.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3fb9b9c24..e3af82369 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: java cache: directories: - - $HOME/.m2 + - $HOME/.m2 jdk: - oraclejdk8 @@ -16,11 +16,13 @@ install: mvn --settings .settings.xml clean install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V script: - mvn --settings .settings.xml verify -DskipTests=false -Dmaven.javadoc.skip=false -B -U + mvn --settings .settings.xml verify -DskipTests=false -Dmaven.javadoc.skip=true -B -U before_deploy: - mvn help:evaluate -N -Dexpression=project.version | grep -v '\[' - export project_version=$(mvn help:evaluate -N -Dexpression=project.version | grep -v '\[') + - sed -i -- "s/\${env.SONATYPE_PASSWORD}/$SONATYPE_PASSWORD/g" .settings.xml + - mvn --settings .settings.xml clean deploy -DskipTests=true -Dmaven.javadoc.skip=false -B -U deploy: provider: releases @@ -38,6 +40,3 @@ deploy: tags: true repo: OpenFeign/feign-form name: $project_version - -after_deploy: - mvn --settings .settings.xml deploy -DskipTests=true -Dmaven.javadoc.skip=true -B -U From e21ed9e578b749993029279ba28743908a1850d5 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sat, 24 Mar 2018 15:39:51 +0300 Subject: [PATCH 53/93] Fix unsecure Travis pull-request builds --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index e3af82369..27f4efe2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,6 @@ language: java -cache: - directories: - - $HOME/.m2 - jdk: - oraclejdk8 @@ -13,14 +9,16 @@ before_install: - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust install: - mvn --settings .settings.xml clean install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V + mvn --settings .settings.xml install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip -B -V script: - mvn --settings .settings.xml verify -DskipTests=false -Dmaven.javadoc.skip=true -B -U + mvn --settings .settings.xml clean verify -DskipTests=false -Dmaven.javadoc.skip=true -Dgpg.skip -B -U before_deploy: - mvn help:evaluate -N -Dexpression=project.version | grep -v '\[' - export project_version=$(mvn help:evaluate -N -Dexpression=project.version | grep -v '\[') + - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import + - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust - sed -i -- "s/\${env.SONATYPE_PASSWORD}/$SONATYPE_PASSWORD/g" .settings.xml - mvn --settings .settings.xml clean deploy -DskipTests=true -Dmaven.javadoc.skip=false -B -U From fcfe0a59d529a8733f74c059e50f0a3c007aa0fb Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sat, 24 Mar 2018 15:41:45 +0300 Subject: [PATCH 54/93] Remove unused block from Travis build --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 27f4efe2b..2383bf29a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,6 @@ language: java jdk: - oraclejdk8 -before_install: - - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import - - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust - install: mvn --settings .settings.xml install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip -B -V From 9c545e39d128d0614ff8506d5e9a3cf325d48749 Mon Sep 17 00:00:00 2001 From: Guillaume Simard Date: Wed, 28 Mar 2018 12:04:19 -0400 Subject: [PATCH 55/93] Added FormData object (#38) * Added FormData object * Fixed PMD checks * Updated README with FormData example * Added FormData creation example --- README.md | 23 +++++++++- feign-form/pom.xml | 8 ++++ .../src/main/java/feign/form/FormData.java | 43 +++++++++++++++++++ .../form/MultipartFormContentProcessor.java | 2 + .../feign/form/multipart/FormDataWriter.java | 35 +++++++++++++++ .../test/java/feign/form/BasicClientTest.java | 8 ++++ .../src/test/java/feign/form/Server.java | 11 +++++ .../src/test/java/feign/form/TestClient.java | 11 +++-- 8 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 feign-form/src/main/java/feign/form/FormData.java create mode 100644 feign-form/src/main/java/feign/form/multipart/FormDataWriter.java diff --git a/README.md b/README.md index fc86e8e62..359223240 100644 --- a/README.md +++ b/README.md @@ -80,18 +80,37 @@ interface SomeApi { ... + // File parameter @RequestLine("POST /send_photo") @Headers("Content-Type: multipart/form-data") void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") File photo); + // byte[] parameter + @RequestLine("POST /send_photo") + @Headers("Content-Type: multipart/form-data") + void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") byte[] photo); + + // FormData parameter + @RequestLine("POST /send_photo") + @Headers("Content-Type: multipart/form-data") + void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") FormData photo); ... } ``` -In example above, we send file in parameter named **photo** with additional field in form **is_public**. +In the example above, the `sendPhoto` method uses the `photo` parameter using three different supported types. -> **IMPORTANT:** You can specify your files in API method by declaring type **File** or **byte[]**. +* `File` will use the File's extension to detect the `Content-Type`. +* `byte[]` will use `application/octet-stream` as `Content-Type`. +* `FormData` will use the `FormData`'s `Content-Type`. + +`FormData` is custom object that wraps a `byte[]` and defines a `Content-Type` like this: + +```java + FormData formData = new FormData("image/png", myDataAsByteArray); + someApi.sendPhoto(true, formData); +``` ### Spring MultipartFile and Spring Cloud Netflix @FeignClient support diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 9cebe2738..f749067d8 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -37,6 +37,14 @@ limitations under the License. 1.6 + + + com.google.code.findbugs + annotations + 3.0.1 + + + diff --git a/feign-form/src/main/java/feign/form/FormData.java b/feign-form/src/main/java/feign/form/FormData.java new file mode 100644 index 000000000..e30a83bc2 --- /dev/null +++ b/feign-form/src/main/java/feign/form/FormData.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * This object encapsulates a byte array and its associated content type. + * Use if if you want to specify the content type of your provided byte array. + */ + +@SuppressFBWarnings({"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public final class FormData { + private final String contentType; + private final byte[] data; + + public FormData (String contentType, byte[] data) { + this.contentType = contentType; + this.data = data; + } + + public String getContentType () { + return contentType; + } + + public byte[] getData () { + return data; + } +} diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index e44207b4e..7cadd5ff3 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -29,6 +29,7 @@ import feign.codec.Encoder; import feign.form.multipart.ByteArrayWriter; import feign.form.multipart.DelegateWriter; +import feign.form.multipart.FormDataWriter; import feign.form.multipart.ManyFilesWriter; import feign.form.multipart.Output; import feign.form.multipart.ParameterWriter; @@ -57,6 +58,7 @@ public class MultipartFormContentProcessor implements ContentProcessor { public MultipartFormContentProcessor (Encoder delegate) { writers = new ArrayList(6); addWriter(new ByteArrayWriter()); + addWriter(new FormDataWriter()); addWriter(new SingleFileWriter()); addWriter(new ManyFilesWriter()); addWriter(new ParameterWriter()); diff --git a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java new file mode 100644 index 000000000..07e70c787 --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.multipart; + +import feign.form.FormData; + +import lombok.val; + +public class FormDataWriter extends AbstractWriter { + @Override + public boolean isApplicable (Object value) { + return value instanceof FormData; + } + + @Override + protected void write (Output output, String key, Object value) throws Exception { + val formData = (FormData) value; + writeFileMetadata(output, key, null, formData.getContentType()); + output.write(formData.getData()); + } +} diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index d78c1b123..4085f5a10 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -28,6 +28,7 @@ import java.nio.file.Paths; import java.util.Map; import lombok.val; +import org.apache.tomcat.util.http.fileupload.IOUtils; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -158,4 +159,11 @@ public void testUnknownTypeFile() throws Exception { val stringResponse = api.uploadUnknownType(path.toFile()); Assert.assertEquals("application/octet-stream", stringResponse); } + + @Test + public void testFormData() throws Exception { + val formData = new FormData("application/custom-type", "Allo".getBytes("UTF-8")); + val stringResponse = api.uploadFormData(formData); + Assert.assertEquals("application/custom-type", stringResponse); + } } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index ad924aafb..fe57c1e6c 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -178,4 +178,15 @@ public ResponseEntity uploadUnknownType (@RequestPart("file") MultipartF : I_AM_A_TEAPOT; return ResponseEntity.status(status).body(file.getContentType()); } + + @PostMapping( + path = "/upload/form_data", + consumes = MULTIPART_FORM_DATA_VALUE + ) + public ResponseEntity uploadFormData (@RequestPart("file") MultipartFile file) { + val status = file != null + ? OK + : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(file.getContentType()); + } } diff --git a/feign-form/src/test/java/feign/form/TestClient.java b/feign-form/src/test/java/feign/form/TestClient.java index d12b73df9..ed4b017f6 100644 --- a/feign-form/src/test/java/feign/form/TestClient.java +++ b/feign-form/src/test/java/feign/form/TestClient.java @@ -16,14 +16,15 @@ package feign.form; +import java.io.File; +import java.util.List; +import java.util.Map; + import feign.Headers; import feign.Param; import feign.QueryMap; import feign.RequestLine; import feign.Response; -import java.io.File; -import java.util.List; -import java.util.Map; /** * @@ -69,4 +70,8 @@ public interface TestClient { @RequestLine("POST /upload/unknown_type") @Headers("Content-Type: multipart/form-data") String uploadUnknownType (@Param("file") File file); + + @RequestLine("POST /upload/form_data") + @Headers("Content-Type: multipart/form-data") + String uploadFormData (@Param("file") FormData formData); } From 1c7bcac5341279a6c18b9bbcb9fd3b4f14794b13 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Wed, 28 Mar 2018 19:33:24 +0300 Subject: [PATCH 56/93] Add FormData holder --- README.md | 6 ++--- feign-form-spring/pom.xml | 4 ++-- feign-form/pom.xml | 2 +- .../src/main/java/feign/form/FormData.java | 23 +++++++------------ .../feign/form/multipart/FormDataWriter.java | 6 +++++ pom.xml | 13 +++++++---- 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 359223240..959cfdea7 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.2.2 + 3.3.0 ... @@ -124,12 +124,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.2.2 + 3.3.0 io.github.openfeign.form feign-form-spring - 3.2.2 + 3.3.0 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 161ce10a9..c39ea0a59 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.2 + 3.3.0 Open Feign Forms Extension for Spring @@ -54,7 +54,7 @@ limitations under the License. org.springframework.cloud spring-cloud-starter-feign - 1.3.5.RELEASE + 1.4.4.RELEASE test diff --git a/feign-form/pom.xml b/feign-form/pom.xml index f749067d8..6111c465d 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.2 + 3.3.0 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/FormData.java b/feign-form/src/main/java/feign/form/FormData.java index e30a83bc2..b704c8a78 100644 --- a/feign-form/src/main/java/feign/form/FormData.java +++ b/feign-form/src/main/java/feign/form/FormData.java @@ -17,27 +17,20 @@ package feign.form; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Value; /** * This object encapsulates a byte array and its associated content type. * Use if if you want to specify the content type of your provided byte array. + * + * @author Guillaume Simard + * @since 24.03.2018 */ - +@Value @SuppressFBWarnings({"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) -public final class FormData { - private final String contentType; - private final byte[] data; - - public FormData (String contentType, byte[] data) { - this.contentType = contentType; - this.data = data; - } +public class FormData { - public String getContentType () { - return contentType; - } + String contentType; - public byte[] getData () { - return data; - } + byte[] data; } diff --git a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java index 07e70c787..952a22547 100644 --- a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java @@ -20,7 +20,13 @@ import lombok.val; +/** + * + * @author Guillaume Simard + * @since 24.03.2018 + */ public class FormDataWriter extends AbstractWriter { + @Override public boolean isApplicable (Object value) { return value instanceof FormData; diff --git a/pom.xml b/pom.xml index 191453e46..4e6bbe65e 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.2.2 + 3.3.0 pom @@ -69,7 +69,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.2.2 + 3.3.0 @@ -90,7 +90,7 @@ limitations under the License. Travis - https://travis-ci.org/infobip/popout + https://travis-ci.org/OpenFeign/feign-form @@ -102,6 +102,9 @@ limitations under the License. Tomasz Juchniewicz tjuchniewicz@gmail.com + + Guillaume Simard + @@ -123,7 +126,7 @@ limitations under the License. io.github.openfeign feign-core - 9.5.1 + 9.6.0 provided @@ -139,7 +142,7 @@ limitations under the License. io.github.openfeign feign-jackson - 9.5.1 + 9.6.0 test From c82e87bce68baf92ae69fa7c259d61196f408f15 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Wed, 3 Oct 2018 12:20:43 +0300 Subject: [PATCH 57/93] Refactoring poms --- feign-form-spring/pom.xml | 7 +++- .../SpringManyMultipartFilesReader.java | 12 +++++- .../test/java/feign/form/BasicClientTest.java | 21 +++++----- pom.xml | 41 ++++++++++--------- 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index c39ea0a59..4c9ae1a13 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -36,11 +36,14 @@ limitations under the License. ${project.groupId} feign-form + ${project.version} compile + org.springframework spring-web + 4.3.18.RELEASE compile @@ -53,8 +56,8 @@ limitations under the License. org.springframework.cloud - spring-cloud-starter-feign - 1.4.4.RELEASE + spring-cloud-starter-openfeign + 1.4.5.RELEASE test diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index c3631155b..f946df936 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -85,7 +85,17 @@ protected boolean supports (Class clazz) { @Override protected MultipartFile[] readInternal (Class clazz, HttpInputMessage inputMessage ) throws IOException { - val boundaryBytes = getMultiPartBoundary(inputMessage.getHeaders().getContentType()); + val headers = inputMessage.getHeaders(); + if (headers == null) { + throw new HttpMessageNotReadableException("There are no headers at all."); + } + + MediaType contentType = headers.getContentType(); + if (contentType == null) { + throw new HttpMessageNotReadableException("Content-Type is missing."); + } + + val boundaryBytes = getMultiPartBoundary(contentType); MultipartStream multipartStream = new MultipartStream(inputMessage.getBody(), boundaryBytes, bufSize, null); val multiparts = new LinkedList(); diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index 4085f5a10..e00885048 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -28,7 +28,6 @@ import java.nio.file.Paths; import java.util.Map; import lombok.val; -import org.apache.tomcat.util.http.fileupload.IOUtils; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -74,7 +73,7 @@ public void testFormException () { @Test public void testUpload () throws Exception { - val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); val stringResponse = api.upload(path.toFile()); @@ -83,7 +82,7 @@ public void testUpload () throws Exception { @Test public void testUploadWithParam () throws Exception { - val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); val stringResponse = api.upload(10, Boolean.TRUE, path.toFile()); @@ -108,9 +107,9 @@ public void testQueryMap () { @Test public void testMultipleFilesArray () throws Exception { - val path1 = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path1)); - val path2 = Paths.get(this.getClass().getClassLoader().getResource("another_file.txt").toURI()); + val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); Assert.assertTrue(Files.exists(path2)); val stringResponse = api.uploadWithArray(new File[] { path1.toFile(), path2.toFile() }); @@ -119,9 +118,9 @@ public void testMultipleFilesArray () throws Exception { @Test public void testMultipleFilesList () throws Exception { - val path1 = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path1)); - val path2 = Paths.get(this.getClass().getClassLoader().getResource("another_file.txt").toURI()); + val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); Assert.assertTrue(Files.exists(path2)); val stringResponse = api.uploadWithList(asList(path1.toFile(), path2.toFile())); @@ -130,9 +129,9 @@ public void testMultipleFilesList () throws Exception { // @Test public void testMultipleManyFiles () throws Exception { - val path1 = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path1)); - val path2 = Paths.get(this.getClass().getClassLoader().getResource("another_file.txt").toURI()); + val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); Assert.assertTrue(Files.exists(path2)); val stringResponse = api.uploadWithManyFiles(path1.toFile(), path2.toFile()); @@ -143,7 +142,7 @@ public void testMultipleManyFiles () throws Exception { public void testUploadWithJson () throws Exception { val dto = new Dto("Artem", 11); - val path = Paths.get(this.getClass().getClassLoader().getResource("file.txt").toURI()); + val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); val response = api.uploadWithJson(dto, path.toFile()); @@ -153,7 +152,7 @@ public void testUploadWithJson () throws Exception { @Test public void testUnknownTypeFile() throws Exception { - val path = Paths.get(this.getClass().getClassLoader().getResource("file.abc").toURI()); + val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.abc").toURI()); Assert.assertTrue(Files.exists(path)); val stringResponse = api.uploadUnknownType(path.toFile()); diff --git a/pom.xml b/pom.xml index 4e6bbe65e..05a717d26 100644 --- a/pom.xml +++ b/pom.xml @@ -26,12 +26,6 @@ limitations under the License. 3.3.0 pom - - org.springframework.boot - spring-boot-starter-parent - 1.5.9.RELEASE - - feign-form feign-form-spring @@ -55,7 +49,7 @@ limitations under the License. Simple encoder for Netflix Feign project, which adds forms support (urlencoded and multipart) https://github.com/OpenFeign/feign-form - 2017 + 2016 @@ -123,31 +117,37 @@ limitations under the License. - - io.github.openfeign - feign-core - 9.6.0 - provided - org.projectlombok lombok + 1.18.2 provided + - org.springframework.boot - spring-boot-starter-web - test + io.github.openfeign + feign-core + 9.5.0 + provided + io.github.openfeign feign-jackson - 9.6.0 + 9.5.0 + test + + + + org.springframework.boot + spring-boot-starter-web + 1.5.14.RELEASE test org.springframework.boot spring-boot-starter-test + 1.5.14.RELEASE test @@ -178,7 +178,7 @@ limitations under the License. analyze-compile - compile + package check @@ -222,7 +222,7 @@ limitations under the License. validate - validate + package check @@ -276,6 +276,7 @@ limitations under the License. maven-compiler-plugin + 3.8.0 @@ -343,7 +344,7 @@ limitations under the License. sign-artifacts - verify + install sign From 37893d3eebd20e3c29283f4ec9a33c831b6071e8 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Wed, 3 Oct 2018 12:59:29 +0300 Subject: [PATCH 58/93] Add a file name field to form data --- README.md | 6 +++--- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- feign-form/src/main/java/feign/form/FormData.java | 2 ++ .../src/main/java/feign/form/multipart/FormDataWriter.java | 2 +- feign-form/src/test/java/feign/form/BasicClientTest.java | 4 ++-- feign-form/src/test/java/feign/form/Server.java | 2 +- pom.xml | 4 ++-- 8 files changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 959cfdea7..c62d21be7 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.3.0 + 3.4.0 ... @@ -124,12 +124,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.3.0 + 3.4.0 io.github.openfeign.form feign-form-spring - 3.3.0 + 3.4.0 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 4c9ae1a13..8e9293e01 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.3.0 + 3.4.0 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 6111c465d..b56ecca9e 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.3.0 + 3.4.0 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/FormData.java b/feign-form/src/main/java/feign/form/FormData.java index b704c8a78..b60067fbe 100644 --- a/feign-form/src/main/java/feign/form/FormData.java +++ b/feign-form/src/main/java/feign/form/FormData.java @@ -32,5 +32,7 @@ public class FormData { String contentType; + String fileName; + byte[] data; } diff --git a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java index 952a22547..5e8431cd1 100644 --- a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java @@ -35,7 +35,7 @@ public boolean isApplicable (Object value) { @Override protected void write (Output output, String key, Object value) throws Exception { val formData = (FormData) value; - writeFileMetadata(output, key, null, formData.getContentType()); + writeFileMetadata(output, key, formData.getFileName(), formData.getContentType()); output.write(formData.getData()); } } diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index e00885048..be70d840e 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -161,8 +161,8 @@ public void testUnknownTypeFile() throws Exception { @Test public void testFormData() throws Exception { - val formData = new FormData("application/custom-type", "Allo".getBytes("UTF-8")); + val formData = new FormData("application/custom-type", "popa.txt", "Allo".getBytes("UTF-8")); val stringResponse = api.uploadFormData(formData); - Assert.assertEquals("application/custom-type", stringResponse); + Assert.assertEquals("popa.txt:application/custom-type", stringResponse); } } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index fe57c1e6c..2343229f9 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -187,6 +187,6 @@ public ResponseEntity uploadFormData (@RequestPart("file") MultipartFile val status = file != null ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).body(file.getContentType()); + return ResponseEntity.status(status).body(file.getOriginalFilename() + ':' + file.getContentType()); } } diff --git a/pom.xml b/pom.xml index 05a717d26..4b72e1470 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.3.0 + 3.4.0 pom @@ -63,7 +63,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.3.0 + 3.4.0 From 6d0a45ad7ce05d08cfab018617244168e1a214e4 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Wed, 3 Oct 2018 13:04:48 +0300 Subject: [PATCH 59/93] Correct README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c62d21be7..c9336a081 100644 --- a/README.md +++ b/README.md @@ -103,12 +103,12 @@ In the example above, the `sendPhoto` method uses the `photo` parameter using th * `File` will use the File's extension to detect the `Content-Type`. * `byte[]` will use `application/octet-stream` as `Content-Type`. -* `FormData` will use the `FormData`'s `Content-Type`. +* `FormData` will use the `FormData`'s `Content-Type` and `fileName`. -`FormData` is custom object that wraps a `byte[]` and defines a `Content-Type` like this: +`FormData` is custom object that wraps a `byte[]` and defines a `Content-Type` and `fileName` like this: ```java - FormData formData = new FormData("image/png", myDataAsByteArray); + FormData formData = new FormData("image/png", "filename.png", myDataAsByteArray); someApi.sendPhoto(true, formData); ``` From b0c4bacf30c937ebd1df10a913efcc722d9964ce Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 29 Oct 2018 13:19:11 +0300 Subject: [PATCH 60/93] Issue48 (#50) * Add default constructor for FormData * Add maven wrapper * update version --- .mvn/wrapper/MavenWrapperDownloader.java | 110 +++++++ .mvn/wrapper/maven-wrapper.properties | 1 + .travis.yml | 10 +- README.md | 6 +- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- .../src/main/java/feign/form/FormData.java | 14 +- mvnw | 286 ++++++++++++++++++ mvnw.cmd | 161 ++++++++++ pom.xml | 4 +- 10 files changed, 582 insertions(+), 14 deletions(-) create mode 100755 .mvn/wrapper/MavenWrapperDownloader.java create mode 100755 .mvn/wrapper/maven-wrapper.properties create mode 100755 mvnw create mode 100755 mvnw.cmd diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100755 index 000000000..fa4f7b499 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,110 @@ +/* +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. +*/ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: : " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 000000000..00d32aab1 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 2383bf29a..441e3ba3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,18 +5,18 @@ jdk: - oraclejdk8 install: - mvn --settings .settings.xml install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip -B -V + ./mvnw --settings .settings.xml install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip -B -V script: - mvn --settings .settings.xml clean verify -DskipTests=false -Dmaven.javadoc.skip=true -Dgpg.skip -B -U + ./mvnw --settings .settings.xml clean verify -DskipTests=false -Dmaven.javadoc.skip=true -Dgpg.skip -B -U before_deploy: - - mvn help:evaluate -N -Dexpression=project.version | grep -v '\[' - - export project_version=$(mvn help:evaluate -N -Dexpression=project.version | grep -v '\[') + - ./mvnw help:evaluate -N -Dexpression=project.version | grep -v '\[' + - export project_version=$(./mvnw help:evaluate -N -Dexpression=project.version | grep -v '\[') - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust - sed -i -- "s/\${env.SONATYPE_PASSWORD}/$SONATYPE_PASSWORD/g" .settings.xml - - mvn --settings .settings.xml clean deploy -DskipTests=true -Dmaven.javadoc.skip=false -B -U + - ./mvnw --settings .settings.xml clean deploy -DskipTests=true -Dmaven.javadoc.skip=false -B -U deploy: provider: releases diff --git a/README.md b/README.md index c9336a081..ad4c49f8c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.4.0 + 3.4.1 ... @@ -124,12 +124,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.4.0 + 3.4.1 io.github.openfeign.form feign-form-spring - 3.4.0 + 3.4.1 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 8e9293e01..5cd93efbd 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.4.0 + 3.4.1 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index b56ecca9e..36ac21451 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.4.0 + 3.4.1 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/FormData.java b/feign-form/src/main/java/feign/form/FormData.java index b60067fbe..8a52c26a8 100644 --- a/feign-form/src/main/java/feign/form/FormData.java +++ b/feign-form/src/main/java/feign/form/FormData.java @@ -16,8 +16,14 @@ package feign.form; +import static lombok.AccessLevel.PRIVATE; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import lombok.Value; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; /** * This object encapsulates a byte array and its associated content type. @@ -26,7 +32,11 @@ * @author Guillaume Simard * @since 24.03.2018 */ -@Value +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = PRIVATE) @SuppressFBWarnings({"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public class FormData { diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..5551fde8e --- /dev/null +++ b/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100755 index 000000000..48363fa60 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 4b72e1470..913f8b4be 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.4.0 + 3.4.1 pom @@ -63,7 +63,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.4.0 + 3.4.1 From 66e1b6f07609fb29c5e7d55aed014116991e7979 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 23 Dec 2018 14:24:24 +0300 Subject: [PATCH 61/93] Fix issue #54, update deps (#55) Add OpenFeign 10 support and update test accordingly --- .mvn/wrapper/maven-wrapper.properties | 2 +- feign-form-spring/pom.xml | 22 +++++++++++++++++-- .../converter/ByteArrayMultipartFile.java | 9 +++++--- .../java/feign/form/feign/spring/Client.java | 6 ++--- .../form/feign/spring/DownloadClient.java | 12 +++++----- .../java/feign/form/feign/spring/Server.java | 2 +- feign-form/pom.xml | 2 +- .../form/MultipartFormContentProcessor.java | 7 +++++- .../form/UrlencodedFormContentProcessor.java | 15 ++++++++----- pom.xml | 12 +++++----- 10 files changed, 60 insertions(+), 29 deletions(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 00d32aab1..cd0d451cc 100755 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip \ No newline at end of file +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 5cd93efbd..7421791f9 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -43,7 +43,7 @@ limitations under the License. org.springframework spring-web - 4.3.18.RELEASE + 5.1.3.RELEASE compile @@ -54,10 +54,17 @@ limitations under the License. compile + + org.springframework.boot + spring-boot-starter-web + 2.1.1.RELEASE + test + org.springframework.cloud spring-cloud-starter-openfeign - 1.4.5.RELEASE + 2.1.0.RC3 + test @@ -99,4 +106,15 @@ limitations under the License. + + + + spring-milestones + Spring Milestones + http://repo.spring.io/milestone + + false + + + diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java index 24328ea42..586cd0dc6 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java @@ -21,10 +21,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import lombok.NonNull; import lombok.Value; -import lombok.val; import org.springframework.web.multipart.MultipartFile; /** @@ -60,11 +60,14 @@ public InputStream getInputStream () { @Override public void transferTo (File destination) throws IOException { - val outputStream = new FileOutputStream(destination); + OutputStream outputStream = null; try { + outputStream = new FileOutputStream(destination); outputStream.write(bytes); } finally { - outputStream.close(); + if (outputStream != null) { + outputStream.close(); + } } } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index 36e291ec8..b45a27b07 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -25,9 +25,9 @@ import java.util.Map; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.web.HttpMessageConverters; -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.cloud.netflix.feign.support.SpringEncoder; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java index 9a4536415..ceaeba87e 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java @@ -5,9 +5,9 @@ import feign.form.spring.converter.SpringManyMultipartFilesReader; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.web.HttpMessageConverters; -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.cloud.netflix.feign.support.SpringDecoder; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.support.SpringDecoder; import org.springframework.context.annotation.Bean; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.bind.annotation.PathVariable; @@ -21,9 +21,9 @@ import lombok.val; @FeignClient( - name = "multipart-download-support-service", - url = "http://localhost:8080", - configuration = DownloadClient.ClientConfiguration.class + name = "multipart-download-support-service", + url = "http://localhost:8080", + configuration = DownloadClient.ClientConfiguration.class ) public interface DownloadClient { diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 5033d5277..e51280693 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -27,7 +27,7 @@ import lombok.SneakyThrows; import lombok.val; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 36ac21451..34735db68 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -41,7 +41,7 @@ limitations under the License. com.google.code.findbugs annotations - 3.0.1 + 3.0.1u2 diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index 7cadd5ff3..a46e099a5 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; +import feign.Request; import feign.RequestTemplate; import feign.codec.Encoder; import feign.form.multipart.ByteArrayWriter; @@ -84,10 +85,14 @@ public void process (RequestTemplate template, Charset charset, Map uploadByteArray (@RequestPart("file") MultipartFil return ResponseEntity.status(status).body(file.getOriginalFilename()); } + @PostMapping( + path = "/upload/byte_array_parameter", + consumes = MULTIPART_FORM_DATA_VALUE + ) + // We just want the request because when there's a filename part of the Content-Disposition header spring + // will treat it as a file (available through getFile()) and when it doesn't have the filename part it's + // available in the parameter (getParameter()) + public ResponseEntity uploadByteArrayParameter (MultipartHttpServletRequest request) { + val status = request.getFile("file") == null && request.getParameter("file") != null + ? OK + : I_AM_A_TEAPOT; + return ResponseEntity.status(status).build(); + } + @PostMapping( path = "/upload/unknown_type", consumes = MULTIPART_FORM_DATA_VALUE From c5af9ed99b45218d86611401d30ae59d26579b43 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 23 Dec 2018 21:27:30 +0300 Subject: [PATCH 63/93] Add user's pojo writer --- .gitignore | 1 + LICENSE | 2 +- feign-form-spring/pom.xml | 12 ++- .../feign/form/spring/SpringFormEncoder.java | 4 +- .../SpringManyMultipartFilesReader.java | 6 +- feign-form-spring/src/main/java/lombok.config | 1 + .../java/feign/form/feign/spring/Client.java | 8 ++ .../form/feign/spring/DownloadClient.java | 12 ++- .../java/feign/form/feign/spring/Server.java | 1 + .../feign/spring/SpringFormEncoderTest.java | 8 +- .../spring/SpringMultipartDecoderTest.java | 2 +- .../src/main/java/feign/form/FormEncoder.java | 6 +- .../form/MultipartFormContentProcessor.java | 40 ++++++++- .../feign/form/multipart/ParameterWriter.java | 2 +- .../java/feign/form/multipart/PojoWriter.java | 81 +++++++++++++++++++ feign-form/src/main/java/lombok.config | 1 + .../test/java/feign/form/BasicClientTest.java | 4 +- .../src/test/java/feign/form/Server.java | 11 +-- .../src/test/java/feign/form/TestClient.java | 4 +- 19 files changed, 173 insertions(+), 33 deletions(-) create mode 100644 feign-form-spring/src/main/java/lombok.config create mode 100644 feign-form/src/main/java/feign/form/multipart/PojoWriter.java create mode 100644 feign-form/src/main/java/lombok.config diff --git a/.gitignore b/.gitignore index c705eab37..fb1bbc6c4 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ nb-configuration.xml log*.txt* .DS_Store .vscode/ +popa.txt diff --git a/LICENSE b/LICENSE index 7f8ced0d1..8e64fe482 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2012 Netflix, Inc. + Copyright 2018 Artem Labazin. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 7421791f9..61ae41023 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -64,8 +64,18 @@ limitations under the License. org.springframework.cloud spring-cloud-starter-openfeign 2.1.0.RC3 - test + + + ${project.groupId} + feign-form-spring + + + + + io.appulse + logging-java + 1.1.0 diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java index bb7ce3293..bb26c7045 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java @@ -54,8 +54,8 @@ public SpringFormEncoder (Encoder delegate) { super(delegate); val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART); - processor.addWriter(new SpringSingleMultipartFileWriter()); - processor.addWriter(new SpringManyMultipartFilesWriter()); + processor.addFirstWriter(new SpringSingleMultipartFileWriter()); + processor.addFirstWriter(new SpringManyMultipartFilesWriter()); } @Override diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index f946df936..bd0bf492b 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -87,12 +87,12 @@ protected MultipartFile[] readInternal (Class clazz, ) throws IOException { val headers = inputMessage.getHeaders(); if (headers == null) { - throw new HttpMessageNotReadableException("There are no headers at all."); + throw new HttpMessageNotReadableException("There are no headers at all.", inputMessage); } MediaType contentType = headers.getContentType(); if (contentType == null) { - throw new HttpMessageNotReadableException("Content-Type is missing."); + throw new HttpMessageNotReadableException("Content-Type is missing.", inputMessage); } val boundaryBytes = getMultiPartBoundary(contentType); @@ -104,7 +104,7 @@ protected MultipartFile[] readInternal (Class clazz, try { multiPart = readMultiPart(multipartStream); } catch (Exception e) { - throw new HttpMessageNotReadableException("Multipart body could not be read.", e); + throw new HttpMessageNotReadableException("Multipart body could not be read.", e, inputMessage); } multiparts.add(multiPart); } diff --git a/feign-form-spring/src/main/java/lombok.config b/feign-form-spring/src/main/java/lombok.config new file mode 100644 index 000000000..26f5d95a3 --- /dev/null +++ b/feign-form-spring/src/main/java/lombok.config @@ -0,0 +1 @@ +lombok.extern.findbugs.addSuppressFBWarnings=true diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index b45a27b07..ec8cace67 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -20,9 +20,12 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import feign.Logger; import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; + import java.util.Map; + import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; @@ -92,5 +95,10 @@ public static class ClientConfiguration { public Encoder feignEncoder () { return new SpringFormEncoder(new SpringEncoder(messageConverters)); } + + @Bean + public Logger.Level feignLogger () { + return Logger.Level.FULL; + } } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java index ceaeba87e..e6d1f6b62 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java @@ -1,6 +1,9 @@ package feign.form.feign.spring; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +import feign.Logger; import feign.codec.Decoder; import feign.form.spring.converter.SpringManyMultipartFilesReader; import org.springframework.beans.factory.ObjectFactory; @@ -16,13 +19,11 @@ import java.util.ArrayList; -import static org.springframework.web.bind.annotation.RequestMethod.GET; - import lombok.val; @FeignClient( name = "multipart-download-support-service", - url = "http://localhost:8080", + url = "http://localhost:8081", configuration = DownloadClient.ClientConfiguration.class ) public interface DownloadClient { @@ -56,5 +57,10 @@ public HttpMessageConverters getObject () { } }); } + + @Bean + public Logger.Level feignLoggerLevel () { + return Logger.Level.FULL; + } } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index e51280693..055ddb56e 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -42,6 +42,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; + /** * @author Tomasz Juchniewicz * @since 22.08.2016 diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 685ebc4df..0404cf628 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -16,11 +16,11 @@ package feign.form.feign.spring; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import static feign.form.util.CharsetUtil.UTF_8; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import java.util.HashMap; -import lombok.val; + import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +29,8 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.junit4.SpringRunner; +import lombok.val; + /** * @author Tomasz Juchniewicz * @since 22.08.2016 @@ -48,7 +50,7 @@ public class SpringFormEncoderTest { private Client client; @Test - public void upload1Test () throws Exception { + public void upload1Test() throws Exception { val folder = "test_folder"; val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); val message = "message test"; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java index 2fef8e3b6..0c83defd9 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java @@ -17,7 +17,7 @@ webEnvironment = DEFINED_PORT, classes = Server.class, properties = { - "server.port=8080", + "server.port=8081", "feign.hystrix.enabled=false" } ) diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index 22a228dc8..0a7b4b396 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -69,8 +69,10 @@ public FormEncoder () { public FormEncoder (Encoder delegate) { this.delegate = delegate; - val list = asList(new MultipartFormContentProcessor(delegate), - new UrlencodedFormContentProcessor()); + val list = asList( + new MultipartFormContentProcessor(delegate), + new UrlencodedFormContentProcessor() + ); processors = new HashMap(list.size(), 1.F); for (ContentProcessor processor : list) { diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index a46e099a5..222ec3a53 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -20,8 +20,8 @@ import static lombok.AccessLevel.PRIVATE; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -34,6 +34,7 @@ import feign.form.multipart.ManyFilesWriter; import feign.form.multipart.Output; import feign.form.multipart.ParameterWriter; +import feign.form.multipart.PojoWriter; import feign.form.multipart.SingleFileWriter; import feign.form.multipart.Writer; @@ -47,7 +48,7 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class MultipartFormContentProcessor implements ContentProcessor { - List writers; + LinkedList writers; Writer defaultPerocessor; @@ -57,12 +58,13 @@ public class MultipartFormContentProcessor implements ContentProcessor { * @param delegate specific delegate encoder for cases, when this processor couldn't handle request parameter. */ public MultipartFormContentProcessor (Encoder delegate) { - writers = new ArrayList(6); + writers = new LinkedList(); addWriter(new ByteArrayWriter()); addWriter(new FormDataWriter()); addWriter(new SingleFileWriter()); addWriter(new ManyFilesWriter()); addWriter(new ParameterWriter()); + addWriter(new PojoWriter()); defaultPerocessor = new DelegateWriter(delegate); } @@ -111,10 +113,42 @@ public final void addWriter (Writer writer) { writers.add(writer); } + /** + * Adds {@link Writer} instance in runtime + * at the beginning of writers list. + * + * @param writer additional writer. + */ + public final void addFirstWriter (Writer writer) { + writers.addFirst(writer); + } + + /** + * Adds {@link Writer} instance in runtime + * at the end of writers list. + * + * @param writer additional writer. + */ + public final void addLastWriter (Writer writer) { + writers.addLast(writer); + } + + /** + * Returns the unmodifiable list of all writers. + * + * @return writers list. + */ public final List getWriters () { return Collections.unmodifiableList(writers); } + /** + * Replaces the writer at the specified position with new element. + * + * @param index index of the element for replace. + * + * @param writer writer to be stored at specified position. + */ public final void setWriter (int index, Writer writer) { writers.set(index, writer); } diff --git a/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java b/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java index 5402e13a6..203b2a45f 100644 --- a/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java @@ -32,7 +32,7 @@ public boolean isApplicable (Object value) { return false; } return value instanceof Number || - value instanceof String; + value instanceof String; } @Override diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java new file mode 100644 index 000000000..3d5330ebf --- /dev/null +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.multipart; + +import static lombok.AccessLevel.PRIVATE; + +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; + +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.val; + +@FieldDefaults(level = PRIVATE, makeFinal = true) +public class PojoWriter extends AbstractWriter { + + ParameterWriter parameterWriter = new ParameterWriter(); + + @Override + public boolean isApplicable (Object object) { + val type = object.getClass(); + val packageName = type.getPackage().getName(); + return !packageName.startsWith("java."); + } + + @Override + public void write (Output output, String boundary, String key, Object value) throws Exception { + val result = new HashMap(); + if (value instanceof Map) { + val map = (Map) value; + for (val entry : map.entrySet()) { + result.put(entry.getKey().toString(), entry.getValue()); + } + } else { + val type = value.getClass(); + for (val field : type.getDeclaredFields()) { + AccessController.doPrivileged(new SetAccessibleAction(field)); + + val fieldValue = field.get(value); + if (fieldValue == null) { + continue; + } + result.put(field.getName(), fieldValue.toString()); + } + } + + for (val entry : result.entrySet()) { + parameterWriter.write(output, boundary, entry.getKey(), entry.getValue()); + } + } + + @RequiredArgsConstructor + @FieldDefaults(level = PRIVATE, makeFinal = true) + private class SetAccessibleAction implements PrivilegedAction { + + Field field; + + @Override + public Object run () { + field.setAccessible(true); + return null; + } + } +} diff --git a/feign-form/src/main/java/lombok.config b/feign-form/src/main/java/lombok.config new file mode 100644 index 000000000..26f5d95a3 --- /dev/null +++ b/feign-form/src/main/java/lombok.config @@ -0,0 +1 @@ +lombok.extern.findbugs.addSuppressFBWarnings=true diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index be70d840e..eb428166f 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -139,13 +139,13 @@ public void testMultipleManyFiles () throws Exception { } @Test - public void testUploadWithJson () throws Exception { + public void testUploadWithDto () throws Exception { val dto = new Dto("Artem", 11); val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); - val response = api.uploadWithJson(dto, path.toFile()); + val response = api.uploadWithDto(dto, path.toFile()); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 0a6b7fa88..809f6cc30 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -27,11 +27,9 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.POST; -import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.List; import lombok.val; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -54,9 +52,6 @@ @SpringBootApplication public class Server { - @Autowired - private ObjectMapper objectMapper; - @RequestMapping(value = "/form", method = POST) public ResponseEntity form (@RequestParam("key1") String key1, @RequestParam("key2") String key2 @@ -145,13 +140,11 @@ public ResponseEntity wildCardMap (@RequestParam("key1") String key1, @ } @PostMapping( - path = "/upload/with_json", + path = "/upload/with_dto", consumes = MULTIPART_FORM_DATA_VALUE ) - public ResponseEntity uploadWithJson (@RequestPart("dto") String dtoString, - @RequestPart("file") MultipartFile file + public ResponseEntity uploadWithDto (Dto dto, @RequestPart("file") MultipartFile file ) throws IOException { - val dto = objectMapper.readValue(dtoString, Dto.class); val status = dto != null && dto.getName().equals("Artem") ? OK : I_AM_A_TEAPOT; diff --git a/feign-form/src/test/java/feign/form/TestClient.java b/feign-form/src/test/java/feign/form/TestClient.java index ed4b017f6..95bc0352b 100644 --- a/feign-form/src/test/java/feign/form/TestClient.java +++ b/feign-form/src/test/java/feign/form/TestClient.java @@ -63,9 +63,9 @@ public interface TestClient { @Headers("Content-Type: multipart/form-data") String uploadWithManyFiles (@Param("files") File file1, @Param("files") File file2); - @RequestLine("POST /upload/with_json") + @RequestLine("POST /upload/with_dto") @Headers("Content-Type: multipart/form-data") - Response uploadWithJson (@Param("dto") Dto dto, @Param("file") File file); + Response uploadWithDto (@Param("1") Dto dto, @Param("file") File file); @RequestLine("POST /upload/unknown_type") @Headers("Content-Type: multipart/form-data") From cc4a95f7408f19f049ea23b09e09e4c0dcfe485f Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 23 Dec 2018 21:35:00 +0300 Subject: [PATCH 64/93] Problems fix --- .../spring/converter/SpringManyMultipartFilesReader.java | 5 +++-- .../src/main/java/feign/form/multipart/AbstractWriter.java | 5 ++++- .../src/main/java/feign/form/multipart/DelegateWriter.java | 3 ++- .../src/main/java/feign/form/multipart/PojoWriter.java | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index bd0bf492b..b1651d678 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -36,6 +36,7 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.util.StringUtils; @@ -121,7 +122,7 @@ private byte[] getMultiPartBoundary (MediaType contentType) { if (!StringUtils.isEmpty(boundaryString)) { return boundaryString.getBytes(UTF_8); } else { - throw new HttpMessageNotReadableException("Content-Type missing boundary information."); + throw new HttpMessageConversionException("Content-Type missing boundary information."); } } @@ -141,7 +142,7 @@ private ByteArrayMultipartFile readMultiPart (MultipartStream multipartStream) t ); if (!contentDisposition.containsKey("form-data")) { - throw new HttpMessageNotReadableException("Content-Disposition is not of type form-data."); + throw new HttpMessageConversionException("Content-Disposition is not of type form-data."); } val bodyStream = new ByteArrayOutputStream(); diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index 7a07d1d74..f8258cfff 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -45,7 +45,10 @@ public void write (Output output, String boundary, String key, Object value) thr * * @throws Exception in case of write errors */ - @SuppressWarnings({"PMD.UncommentedEmptyMethodBody", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) + @SuppressWarnings({ + "PMD.UncommentedEmptyMethodBody", + "PMD.EmptyMethodInAbstractClassShouldBeAbstract" + }) protected void write (Output output, String key, Object value) throws Exception { } diff --git a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java index d2993011e..cced4e0b9 100644 --- a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java @@ -46,7 +46,8 @@ public boolean isApplicable (Object value) { protected void write (Output output, String key, Object value) throws Exception { val fake = new RequestTemplate(); delegate.encode(value, value.getClass(), fake); - val string = new String(fake.body(), output.getCharset()).replaceAll("\n", ""); + val bytes = fake.requestBody().asBytes(); + val string = new String(bytes, output.getCharset()).replaceAll("\n", ""); parameterWriter.write(output, key, string); } } diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java index 3d5330ebf..cb4e238fd 100644 --- a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -41,6 +41,7 @@ public boolean isApplicable (Object object) { } @Override + @SuppressWarnings("unchecked") public void write (Output output, String boundary, String key, Object value) throws Exception { val result = new HashMap(); if (value instanceof Map) { From be970672e43ac87e8497d7aea0c8621d0a707f00 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 24 Dec 2018 11:12:06 +0300 Subject: [PATCH 65/93] Add Pojo writer --- README.md | 6 +- feign-form-spring/pom.xml | 2 +- .../java/feign/form/feign/spring/Client.java | 8 +++ .../java/feign/form/feign/spring/Dto.java | 43 ++++++++++++++ .../java/feign/form/feign/spring/Server.java | 15 ++++- .../feign/spring/SpringFormEncoderTest.java | 13 +++- feign-form/pom.xml | 2 +- .../src/main/java/feign/form/FormEncoder.java | 59 ++++++++++++++++++- .../form/MultipartFormContentProcessor.java | 2 +- .../java/feign/form/multipart/PojoWriter.java | 45 +++++++------- pom.xml | 4 +- 11 files changed, 166 insertions(+), 33 deletions(-) create mode 100644 feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java diff --git a/README.md b/README.md index ad4c49f8c..08dfbd25e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Include the dependency to your project's pom.xml file: io.github.openfeign.form feign-form - 3.4.1 + 3.5.0 ... @@ -124,12 +124,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.4.1 + 3.5.0 io.github.openfeign.form feign-form-spring - 3.4.1 + 3.5.0 ... diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 61ae41023..ef38ac23b 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.4.1 + 3.5.0 Open Feign Forms Extension for Spring diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index ec8cace67..0f8f8eae8 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -21,6 +21,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST; import feign.Logger; +import feign.Response; import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; @@ -86,6 +87,13 @@ String upload4 (@PathVariable("id") String id, @RequestBody Map map, @RequestParam("userName") String userName); + @RequestMapping( + path = "/multipart/upload5", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + Response upload5 (Dto dto); + public static class ClientConfiguration { @Autowired diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java new file mode 100644 index 000000000..aa963d0e8 --- /dev/null +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.feign.spring; + +import static lombok.AccessLevel.PRIVATE; + +import java.io.Serializable; + +import org.springframework.web.multipart.MultipartFile; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = PRIVATE) +public class Dto implements Serializable { + + private static final long serialVersionUID = -4218390863359894943L; + + String field1; + + int field2; + + MultipartFile file; +} diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 055ddb56e..8f35b4a93 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -20,8 +20,9 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; -import static feign.form.util.CharsetUtil.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; +import java.io.IOException; import java.util.Map; import lombok.SneakyThrows; @@ -101,6 +102,18 @@ public String upload4 (@PathVariable("id") String id, return userName + ':' + id + ':' + map.size(); } + @RequestMapping( + path = "/multipart/upload5", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + void upload5 (Dto dto) throws IOException { + assert "field 1 value".equals(dto.getField1()); + assert 42 == dto.getField2(); + + assert "Hello world".equals(new String(dto.getFile().getBytes(), UTF_8)); + } + @RequestMapping( value = "/multipart/download/{fileId}", method = GET, diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 0404cf628..7348799c1 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -17,6 +17,7 @@ package feign.form.feign.spring; import static feign.form.util.CharsetUtil.UTF_8; +import static org.junit.Assert.assertEquals; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import java.util.HashMap; @@ -41,7 +42,8 @@ classes = Server.class, properties = { "server.port=8080", - "feign.hystrix.enabled=false" + "feign.hystrix.enabled=false", + "logging.level.feign.form.feign.spring.Client=DEBUG" } ) public class SpringFormEncoderTest { @@ -100,4 +102,13 @@ public void upload4Test () throws Exception { Assert.assertEquals(userName + ':' + id + ':' + map.size(), response); } + + @Test + public void upload5Test () throws Exception { + val file = new MockMultipartFile("popa.txt", "Hello world".getBytes(UTF_8)); + val dto = new Dto("field 1 value", 42, file); + + val response = client.upload5(dto); + assertEquals(200, response.status()); + } } diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 34735db68..c171964f8 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -27,7 +27,7 @@ limitations under the License. io.github.openfeign.form parent - 3.4.1 + 3.5.0 Open Feign Forms Core diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index 0a7b4b396..da42f606a 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -17,11 +17,16 @@ package feign.form; import static feign.form.util.CharsetUtil.UTF_8; +import static java.lang.reflect.Modifier.isFinal; +import static java.lang.reflect.Modifier.isStatic; import static java.util.Arrays.asList; import static lombok.AccessLevel.PRIVATE; +import java.lang.reflect.Field; import java.lang.reflect.Type; import java.nio.charset.Charset; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -31,6 +36,8 @@ import feign.codec.EncodeException; import feign.codec.Encoder; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.experimental.FieldDefaults; import lombok.val; @@ -85,13 +92,22 @@ public FormEncoder (Encoder delegate) { public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException { String contentTypeValue = getContentTypeValue(template.headers()); val contentType = ContentType.of(contentTypeValue); - if (!MAP_STRING_WILDCARD.equals(bodyType) || !processors.containsKey(contentType)) { + if (!processors.containsKey(contentType)) { + delegate.encode(object, bodyType, template); + return; + } + + Map data; + if (MAP_STRING_WILDCARD.equals(bodyType)) { + data = (Map) object; + } else if (isUserPojo(object)) { + data = convertPojoToMap(object); + } else { delegate.encode(object, bodyType, template); return; } val charset = getCharset(contentTypeValue); - val data = (Map) object; try { processors.get(contentType).process(template, charset, data); } catch (Exception ex) { @@ -132,4 +148,43 @@ private Charset getCharset (String contentTypeValue) { ? Charset.forName(matcher.group(1)) : UTF_8; } + + private boolean isUserPojo (Object object) { + val type = object.getClass(); + val packageName = type.getPackage().getName(); + return !packageName.startsWith("java."); + } + + @SneakyThrows + private Map convertPojoToMap (Object object) { + Map result = new HashMap(); + val type = object.getClass(); + for (val field : type.getDeclaredFields()) { + val modifiers = field.getModifiers(); + if (isFinal(modifiers) || isStatic(modifiers)) { + continue; + } + AccessController.doPrivileged(new SetAccessibleAction(field)); + + val fieldValue = field.get(object); + if (fieldValue == null) { + continue; + } + result.put(field.getName(), fieldValue); + } + return result; + } + + @RequiredArgsConstructor + @FieldDefaults(level = PRIVATE, makeFinal = true) + private class SetAccessibleAction implements PrivilegedAction { + + Field field; + + @Override + public Object run () { + field.setAccessible(true); + return null; + } + } } diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index 222ec3a53..b69e233bc 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -64,7 +64,7 @@ public MultipartFormContentProcessor (Encoder delegate) { addWriter(new SingleFileWriter()); addWriter(new ManyFilesWriter()); addWriter(new ParameterWriter()); - addWriter(new PojoWriter()); + addWriter(new PojoWriter(writers)); defaultPerocessor = new DelegateWriter(delegate); } diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java index cb4e238fd..fc4b0c578 100644 --- a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -21,17 +21,17 @@ import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.val; +@RequiredArgsConstructor @FieldDefaults(level = PRIVATE, makeFinal = true) public class PojoWriter extends AbstractWriter { - ParameterWriter parameterWriter = new ParameterWriter(); + List writers; @Override public boolean isApplicable (Object object) { @@ -41,30 +41,33 @@ public boolean isApplicable (Object object) { } @Override - @SuppressWarnings("unchecked") - public void write (Output output, String boundary, String key, Object value) throws Exception { - val result = new HashMap(); - if (value instanceof Map) { - val map = (Map) value; - for (val entry : map.entrySet()) { - result.put(entry.getKey().toString(), entry.getValue()); + public void write (Output output, String boundary, String key, Object object) throws Exception { + val type = object.getClass(); + for (val field : type.getDeclaredFields()) { + AccessController.doPrivileged(new SetAccessibleAction(field)); + + val value = field.get(object); + if (value == null) { + continue; } - } else { - val type = value.getClass(); - for (val field : type.getDeclaredFields()) { - AccessController.doPrivileged(new SetAccessibleAction(field)); - val fieldValue = field.get(value); - if (fieldValue == null) { - continue; - } - result.put(field.getName(), fieldValue.toString()); + val writer = findApplicableWriter(value); + if (writer == null) { + continue; } + + val name = field.getName(); + writer.write(output, boundary, name, value); } + } - for (val entry : result.entrySet()) { - parameterWriter.write(output, boundary, entry.getKey(), entry.getValue()); + private Writer findApplicableWriter (Object value) { + for (val writer : writers) { + if (writer.isApplicable(value)) { + return writer; + } } + return null; } @RequiredArgsConstructor diff --git a/pom.xml b/pom.xml index 13b65918b..f2a92a17d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ limitations under the License. io.github.openfeign.form parent - 3.4.1 + 3.5.0 pom @@ -63,7 +63,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.4.1 + 3.5.0 From f66891345f1a11706bdc57df731dcd360901e79f Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 24 Dec 2018 12:00:30 +0300 Subject: [PATCH 66/93] Update deps --- .codestyle/checkstyle.xml | 31 +-- .codestyle/findbugs.xml | 8 +- .codestyle/pmd.xml | 178 +++++------------- feign-form-spring/pom.xml | 21 +-- feign-form/pom.xml | 13 +- .../test/java/feign/form/BasicClientTest.java | 51 ++--- .../src/test/java/feign/form/Server.java | 2 +- .../test/java/feign/form/WildCardMapTest.java | 120 ++++++------ pom.xml | 124 ++++++++++-- 9 files changed, 286 insertions(+), 262 deletions(-) diff --git a/.codestyle/checkstyle.xml b/.codestyle/checkstyle.xml index be7c471a6..c25fe200c 100644 --- a/.codestyle/checkstyle.xml +++ b/.codestyle/checkstyle.xml @@ -4,11 +4,14 @@ "http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd"> @@ -40,9 +44,11 @@ limitations under the License. - - - + + + + + @@ -57,8 +63,11 @@ limitations under the License. + + + @@ -81,7 +90,6 @@ limitations under the License. - @@ -162,7 +170,7 @@ limitations under the License. - + @@ -179,7 +187,7 @@ limitations under the License. - + @@ -225,7 +233,7 @@ limitations under the License. + value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LE, LITERAL_INSTANCEOF, LT, MINUS, MOD, NOT_EQUAL, QUESTION, SL, SR, STAR"/> @@ -306,9 +314,6 @@ limitations under the License. - - - @@ -347,7 +352,6 @@ limitations under the License. - @@ -357,7 +361,6 @@ limitations under the License. - @@ -375,7 +378,7 @@ limitations under the License. - + diff --git a/.codestyle/findbugs.xml b/.codestyle/findbugs.xml index eaed6b42c..d3858eb27 100644 --- a/.codestyle/findbugs.xml +++ b/.codestyle/findbugs.xml @@ -1,10 +1,13 @@ + + + + + + + + + + + + + + + + + - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - + + - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - + + + - - - - - + + diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index ef38ac23b..748774925 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -65,18 +65,18 @@ limitations under the License. spring-cloud-starter-openfeign 2.1.0.RC3 test - - - ${project.groupId} - feign-form-spring - - + - + @@ -102,14 +102,13 @@ limitations under the License. - org.codehaus.mojo - findbugs-maven-plugin + com.github.spotbugs + spotbugs-maven-plugin org.apache.maven.plugins maven-pmd-plugin - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/feign-form/pom.xml b/feign-form/pom.xml index c171964f8..93a8dfd64 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -37,14 +37,6 @@ limitations under the License. 1.6 - - - com.google.code.findbugs - annotations - 3.0.1u2 - - - @@ -68,14 +60,13 @@ limitations under the License. - org.codehaus.mojo - findbugs-maven-plugin + com.github.spotbugs + spotbugs-maven-plugin org.apache.maven.plugins maven-pmd-plugin - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index eb428166f..43854e957 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -16,23 +16,24 @@ package feign.form; -import static java.util.Collections.singletonMap; import static feign.Logger.Level.FULL; import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; -import feign.Feign; -import feign.jackson.JacksonEncoder; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Map; -import lombok.val; + +import feign.Feign; +import feign.jackson.JacksonEncoder; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import lombok.val; /** * @author Artem Labazin @@ -45,10 +46,10 @@ ) public class BasicClientTest { - private static final TestClient api; + private static final TestClient API; static { - api = Feign.builder() + API = Feign.builder() .encoder(new FormEncoder(new JacksonEncoder())) .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) .logLevel(FULL) @@ -57,7 +58,7 @@ public class BasicClientTest { @Test public void testForm () { - val response = api.form("1", "1"); + val response = API.form("1", "1"); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); @@ -65,7 +66,7 @@ public void testForm () { @Test public void testFormException () { - val response = api.form("1", "2"); + val response = API.form("1", "2"); Assert.assertNotNull(response); Assert.assertEquals(400, response.status()); @@ -76,7 +77,7 @@ public void testUpload () throws Exception { val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); - val stringResponse = api.upload(path.toFile()); + val stringResponse = API.upload(path.toFile()); Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); } @@ -85,14 +86,14 @@ public void testUploadWithParam () throws Exception { val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); - val stringResponse = api.upload(10, Boolean.TRUE, path.toFile()); + val stringResponse = API.upload(10, Boolean.TRUE, path.toFile()); Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); } @Test public void testJson () { val dto = new Dto("Artem", 11); - val stringResponse = api.json(dto); + val stringResponse = API.json(dto); Assert.assertEquals("ok", stringResponse); } @@ -101,7 +102,7 @@ public void testJson () { public void testQueryMap () { Map value = singletonMap("filter", (Object) asList("one", "two", "three", "four")); - val stringResponse = api.queryMap(value); + val stringResponse = API.queryMap(value); Assert.assertEquals("4", stringResponse); } @@ -112,7 +113,7 @@ public void testMultipleFilesArray () throws Exception { val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); Assert.assertTrue(Files.exists(path2)); - val stringResponse = api.uploadWithArray(new File[] { path1.toFile(), path2.toFile() }); + val stringResponse = API.uploadWithArray(new File[] { path1.toFile(), path2.toFile() }); Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); } @@ -123,7 +124,7 @@ public void testMultipleFilesList () throws Exception { val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); Assert.assertTrue(Files.exists(path2)); - val stringResponse = api.uploadWithList(asList(path1.toFile(), path2.toFile())); + val stringResponse = API.uploadWithList(asList(path1.toFile(), path2.toFile())); Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); } @@ -134,7 +135,7 @@ public void testMultipleManyFiles () throws Exception { val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); Assert.assertTrue(Files.exists(path2)); - val stringResponse = api.uploadWithManyFiles(path1.toFile(), path2.toFile()); + val stringResponse = API.uploadWithManyFiles(path1.toFile(), path2.toFile()); Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); } @@ -145,24 +146,24 @@ public void testUploadWithDto () throws Exception { val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); Assert.assertTrue(Files.exists(path)); - val response = api.uploadWithDto(dto, path.toFile()); + val response = API.uploadWithDto(dto, path.toFile()); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); } @Test - public void testUnknownTypeFile() throws Exception { - val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.abc").toURI()); - Assert.assertTrue(Files.exists(path)); + public void testUnknownTypeFile () throws Exception { + val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.abc").toURI()); + Assert.assertTrue(Files.exists(path)); - val stringResponse = api.uploadUnknownType(path.toFile()); - Assert.assertEquals("application/octet-stream", stringResponse); + val stringResponse = API.uploadUnknownType(path.toFile()); + Assert.assertEquals("application/octet-stream", stringResponse); } @Test - public void testFormData() throws Exception { - val formData = new FormData("application/custom-type", "popa.txt", "Allo".getBytes("UTF-8")); - val stringResponse = api.uploadFormData(formData); - Assert.assertEquals("popa.txt:application/custom-type", stringResponse); + public void testFormData () throws Exception { + val formData = new FormData("application/custom-type", "popa.txt", "Allo".getBytes("UTF-8")); + val stringResponse = API.uploadFormData(formData); + Assert.assertEquals("popa.txt:application/custom-type", stringResponse); } } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 809f6cc30..b48451faf 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -50,7 +50,7 @@ */ @Controller @SpringBootApplication -public class Server { +public final class Server { @RequestMapping(value = "/form", method = POST) public ResponseEntity form (@RequestParam("key1") String key1, diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index 88749ac09..1894c7d45 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -16,73 +16,77 @@ package feign.form; -import feign.*; +import static feign.Logger.Level.FULL; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +import java.util.HashMap; +import java.util.Map; + +import feign.Feign; +import feign.Headers; +import feign.Logger; +import feign.RequestLine; +import feign.Response; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; -import java.util.HashMap; -import java.util.Map; - -import static feign.Logger.Level.FULL; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - @RunWith(SpringRunner.class) @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class + webEnvironment = DEFINED_PORT, + classes = Server.class ) public class WildCardMapTest { - private static FormUrlEncodedApi API; - - @BeforeClass - public static void configureClient () { - API = Feign.builder() - .encoder(new FormEncoder()) - .logger(new Logger.JavaLogger().appendToFile("log.txt")) - .logLevel(FULL) - .target(FormUrlEncodedApi.class, "http://localhost:8080"); - } - - @Test - public void testOk () { - Map param = new HashMap() { - - private static final long serialVersionUID = 3109256773218160485L; - - { - put("key1", "1"); - put("key2", "1"); - } - }; - Response response = API.wildCardMap(param); - Assert.assertEquals(200, response.status()); - } - - @Test - public void testBadRequest () { - Map param = new HashMap() { - - private static final long serialVersionUID = 3109256773218160485L; - - { - - put("key1", "1"); - put("key2", "2"); - } - }; - Response response = API.wildCardMap(param); - Assert.assertEquals(418, response.status()); - } - - interface FormUrlEncodedApi { - - @RequestLine("POST /wild-card-map") - @Headers("Content-Type: application/x-www-form-urlencoded") - Response wildCardMap (Map param); - } + private static FormUrlEncodedApi API; + + @BeforeClass + public static void configureClient () { + API = Feign.builder() + .encoder(new FormEncoder()) + .logger(new Logger.JavaLogger().appendToFile("log.txt")) + .logLevel(FULL) + .target(FormUrlEncodedApi.class, "http://localhost:8080"); + } + + @Test + public void testOk () { + Map param = new HashMap() { + + private static final long serialVersionUID = 3109256773218160485L; + + { + put("key1", "1"); + put("key2", "1"); + } + }; + Response response = API.wildCardMap(param); + Assert.assertEquals(200, response.status()); + } + + @Test + public void testBadRequest () { + Map param = new HashMap() { + + private static final long serialVersionUID = 3109256773218160485L; + + { + + put("key1", "1"); + put("key2", "2"); + } + }; + Response response = API.wildCardMap(param); + Assert.assertEquals(418, response.status()); + } + + interface FormUrlEncodedApi { + + @RequestLine("POST /wild-card-map") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response wildCardMap (Map param); + } } diff --git a/pom.xml b/pom.xml index f2a92a17d..c630d637d 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,58 @@ limitations under the License. 2.1.1.RELEASE test + + + org.junit.jupiter + junit-jupiter-engine + 5.3.2 + test + + + org.junit.jupiter + junit-jupiter-params + 5.3.2 + test + + + + org.assertj + assertj-core + 3.11.1 + test + + + + org.mockito + mockito-core + 2.23.4 + test + + + org.mockito + mockito-junit-jupiter + 2.23.4 + test + + + + net.jcip + jcip-annotations + 1.0 + provided + + + com.github.spotbugs + spotbugs-annotations + 3.1.9 + provided + + + com.google.code.findbugs + jsr305 + 3.0.2 + provided + @@ -166,9 +218,9 @@ limitations under the License. - org.codehaus.mojo - findbugs-maven-plugin - 3.0.5 + com.github.spotbugs + spotbugs-maven-plugin + 3.1.8 Max Low @@ -177,8 +229,8 @@ limitations under the License. - analyze-compile - package + spotbugs-validation + verify check @@ -188,11 +240,13 @@ limitations under the License. org.apache.maven.plugins maven-pmd-plugin - 3.8 + 3.11.0 ${project.build.sourceEncoding} ${maven.compiler.source} - true + true + true + true false ${project.basedir}/../.codestyle/pmd.xml @@ -200,7 +254,8 @@ limitations under the License. - package + pmd-validation + verify check @@ -211,7 +266,7 @@ limitations under the License. org.apache.maven.plugins maven-checkstyle-plugin - 2.17 + 3.0.0 com.puppycrawl.tools @@ -221,8 +276,8 @@ limitations under the License. - validate - package + checkstyle-validation + verify check @@ -233,7 +288,7 @@ limitations under the License. true false codestyleFolder=${project.basedir}/../.codestyle - + true @@ -256,12 +311,55 @@ limitations under the License. + + pl.project13.maven + git-commit-id-plugin + 2.2.5 + + + git-infos + + revision + + + + + true + false + true + false + git + + ${project.build.outputDirectory}/git.properties + + yyyy-MM-dd'T'HH:mm:ssZ + ${project.basedir}/.git + + git.build.user.* + git.build.host + git.commit.user.* + git.total.commit.count + git.closest.tag.commit.count + git.closest.tag.name + + + + org.apache.maven.plugins maven-javadoc-plugin - 2.10.4 + 3.0.1 + -Xdoclint:none -Xdoclint:none + true + UTF-8 + UTF-8 + UTF-8 + true + protected + ${java.version} + true From 8561d4fc9c1d26782c58998817f2307b958fda5d Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 24 Dec 2018 14:37:30 +0300 Subject: [PATCH 67/93] Fix checkstyle --- feign-form-spring/pom.xml | 9 +- .../SpringManyMultipartFilesWriter.java | 3 +- .../SpringSingleMultipartFileWriter.java | 14 +- .../spring/converter/IgnoreKeyCaseMap.java | 6 +- .../SpringManyMultipartFilesReader.java | 7 +- .../java/feign/form/feign/spring/Client.java | 9 +- .../form/feign/spring/DownloadClient.java | 69 +++---- .../java/feign/form/feign/spring/Dto.java | 3 +- .../java/feign/form/feign/spring/Server.java | 168 +++++++++--------- .../feign/spring/SpringFormEncoderTest.java | 5 +- .../spring/SpringMultipartDecoderTest.java | 65 ++++--- .../SpringManyMultipartFilesReaderTest.java | 102 ++++++----- .../java/feign/form/ContentProcessor.java | 5 +- .../src/main/java/feign/form/ContentType.java | 8 +- .../src/main/java/feign/form/FormEncoder.java | 61 +------ .../form/MultipartFormContentProcessor.java | 36 ++-- .../form/UrlencodedFormContentProcessor.java | 7 +- .../feign/form/multipart/AbstractWriter.java | 5 +- .../feign/form/multipart/ByteArrayWriter.java | 4 +- .../feign/form/multipart/DelegateWriter.java | 3 +- .../feign/form/multipart/FormDataWriter.java | 3 +- .../feign/form/multipart/ManyFilesWriter.java | 3 +- .../feign/form/multipart/ParameterWriter.java | 3 +- .../java/feign/form/multipart/PojoWriter.java | 49 ++--- .../form/multipart/SingleFileWriter.java | 20 ++- .../java/feign/form/multipart/Writer.java | 6 +- .../java/feign/form/util/CharsetUtil.java | 4 +- .../main/java/feign/form/util/PojoUtil.java | 91 ++++++++++ .../test/java/feign/form/BasicClientTest.java | 13 +- .../java/feign/form/ByteArrayClientTest.java | 14 +- .../java/feign/form/CustomClientTest.java | 16 +- feign-form/src/test/java/feign/form/Dto.java | 1 + .../src/test/java/feign/form/Server.java | 43 ++--- .../test/java/feign/form/WildCardMapTest.java | 10 +- pom.xml | 6 + 35 files changed, 469 insertions(+), 402 deletions(-) create mode 100644 feign-form/src/main/java/feign/form/util/PojoUtil.java diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 748774925..1fe8f8dba 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -65,18 +65,13 @@ limitations under the License. spring-cloud-starter-openfeign 2.1.0.RC3 test - + - diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java index ae1d9acf4..2080e9b4a 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java @@ -18,6 +18,7 @@ import static lombok.AccessLevel.PRIVATE; +import feign.codec.EncodeException; import feign.form.multipart.AbstractWriter; import feign.form.multipart.Output; @@ -35,7 +36,7 @@ public class SpringManyMultipartFilesWriter extends AbstractWriter { SpringSingleMultipartFileWriter fileWriter = new SpringSingleMultipartFileWriter(); @Override - public void write (Output output, String boundary, String key, Object value) throws Exception { + public void write (Output output, String boundary, String key, Object value) throws EncodeException { if (value instanceof MultipartFile[]) { val files = (MultipartFile[]) value; for (val file : files) { diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java index 7e5318e35..324fb8204 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java @@ -16,9 +16,11 @@ package feign.form.spring; +import java.io.IOException; + +import feign.codec.EncodeException; import feign.form.multipart.AbstractWriter; import feign.form.multipart.Output; - import lombok.val; import org.springframework.web.multipart.MultipartFile; @@ -34,11 +36,17 @@ public boolean isApplicable (Object value) { } @Override - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { val file = (MultipartFile) value; writeFileMetadata(output, key, file.getOriginalFilename(), file.getContentType()); - output.write(file.getBytes()); + byte[] bytes; + try { + bytes = file.getBytes(); + } catch (IOException ex) { + throw new EncodeException("Getting multipart file's content bytes error", ex); + } + output.write(bytes); } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java index 10e5cb4ee..a67122bcc 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java @@ -28,9 +28,9 @@ final class IgnoreKeyCaseMap extends HashMap { private static final long serialVersionUID = -2321516556941546746L; private static String normalizeKey (Object key) { - return key != null - ? key.toString().toUpperCase(new Locale("en_US")) - : null; + return key == null + ? null + : key.toString().toUpperCase(new Locale("en_US")); } @Override diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index b1651d678..433d415fc 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -109,7 +109,7 @@ protected MultipartFile[] readInternal (Class clazz, } multiparts.add(multiPart); } - return multiparts.toArray(new ByteArrayMultipartFile[multiparts.size()]); + return multiparts.toArray(new ByteArrayMultipartFile[0]); } @Override @@ -119,11 +119,10 @@ protected void writeInternal (MultipartFile[] byteArrayMultipartFiles, HttpOutpu private byte[] getMultiPartBoundary (MediaType contentType) { val boundaryString = unquote(contentType.getParameter("boundary")); - if (!StringUtils.isEmpty(boundaryString)) { - return boundaryString.getBytes(UTF_8); - } else { + if (StringUtils.isEmpty(boundaryString)) { throw new HttpMessageConversionException("Content-Type missing boundary information."); } + return boundaryString.getBytes(UTF_8); } private ByteArrayMultipartFile readMultiPart (MultipartStream multipartStream) throws IOException { diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index 0f8f8eae8..95dc9d4da 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -20,13 +20,12 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import java.util.Map; + import feign.Logger; import feign.Response; import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; - -import java.util.Map; - import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; @@ -94,7 +93,7 @@ String upload4 (@PathVariable("id") String id, ) Response upload5 (Dto dto); - public static class ClientConfiguration { + class ClientConfiguration { @Autowired private ObjectFactory messageConverters; @@ -106,7 +105,7 @@ public Encoder feignEncoder () { @Bean public Logger.Level feignLogger () { - return Logger.Level.FULL; + return Logger.Level.FULL; } } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java index e6d1f6b62..388848ee2 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java @@ -1,11 +1,27 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.feign.spring; -import static org.springframework.web.bind.annotation.RequestMethod.GET; +import java.util.ArrayList; import feign.Logger; import feign.codec.Decoder; import feign.form.spring.converter.SpringManyMultipartFilesReader; +import lombok.val; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; @@ -17,10 +33,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; - -import lombok.val; - @FeignClient( name = "multipart-download-support-service", url = "http://localhost:8081", @@ -28,39 +40,36 @@ ) public interface DownloadClient { - @RequestMapping( - value = "/multipart/download/{fileId}", - method = GET - ) - MultipartFile[] download (@PathVariable("fileId") String fileId); + @RequestMapping("/multipart/download/{fileId}") + MultipartFile[] download (@PathVariable("fileId") String fileId); - class ClientConfiguration { + class ClientConfiguration { - @Autowired - private ObjectFactory messageConverters; + @Autowired + private ObjectFactory messageConverters; - @Bean - public Decoder feignDecoder () { - val springConverters = messageConverters.getObject().getConverters(); - val decoderConverters = new ArrayList>(springConverters.size() + 1); + @Bean + public Decoder feignDecoder () { + val springConverters = messageConverters.getObject().getConverters(); + val decoderConverters = new ArrayList>(springConverters.size() + 1); - decoderConverters.addAll(springConverters); - decoderConverters.add(new SpringManyMultipartFilesReader(4096)); + decoderConverters.addAll(springConverters); + decoderConverters.add(new SpringManyMultipartFilesReader(4096)); - val httpMessageConverters = new HttpMessageConverters(decoderConverters); + val httpMessageConverters = new HttpMessageConverters(decoderConverters); - return new SpringDecoder(new ObjectFactory() { + return new SpringDecoder(new ObjectFactory() { - @Override - public HttpMessageConverters getObject () { - return httpMessageConverters; - } - }); + @Override + public HttpMessageConverters getObject () { + return httpMessageConverters; } + }); + } - @Bean - public Logger.Level feignLoggerLevel () { - return Logger.Level.FULL; - } + @Bean + public Logger.Level feignLoggerLevel () { + return Logger.Level.FULL; } + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java index aa963d0e8..b66fde787 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java @@ -20,12 +20,11 @@ import java.io.Serializable; -import org.springframework.web.multipart.MultipartFile; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.web.multipart.MultipartFile; @Data @NoArgsConstructor diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 8f35b4a93..79859c41c 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -16,11 +16,11 @@ package feign.form.feign.spring; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; -import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.util.Map; @@ -51,90 +51,88 @@ @RestController @EnableFeignClients @SpringBootApplication +@SuppressWarnings("checkstyle:DesignForExtension") public class Server { - @RequestMapping( - value = "/multipart/upload1/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE - ) - @SneakyThrows - public String upload1 (@PathVariable("folder") String folder, - @RequestPart MultipartFile file, - @RequestParam(value = "message", required = false) String message - ) { - return new String(file.getBytes()) + ':' + message + ':' + folder; - } - - @RequestMapping( - value = "/multipart/upload2/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE - ) - @SneakyThrows - public String upload2 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message - ) { - return new String(file.getBytes()) + ':' + message + ':' + folder; - } - - @RequestMapping( - value = "/multipart/upload3/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE - ) - public String upload3 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message - ) { - return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; - } - - @RequestMapping( - path = "/multipart/upload4/{id}", - method = POST - ) - public String upload4 (@PathVariable("id") String id, - @RequestBody Map map, - @RequestParam String userName - ) { - return userName + ':' + id + ':' + map.size(); - } - - @RequestMapping( - path = "/multipart/upload5", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE - ) - void upload5 (Dto dto) throws IOException { - assert "field 1 value".equals(dto.getField1()); - assert 42 == dto.getField2(); - - assert "Hello world".equals(new String(dto.getFile().getBytes(), UTF_8)); - } - - @RequestMapping( - value = "/multipart/download/{fileId}", - method = GET, - produces = MULTIPART_FORM_DATA_VALUE - ) - public MultiValueMap download (@PathVariable("fileId") String fileId) { - val multiParts = new LinkedMultiValueMap(); - - val infoString = "The text for file ID " + fileId + ". Testing unicode €"; - val infoPartheader = new HttpHeaders(); - infoPartheader.setContentType(new MediaType("text", "plain", UTF_8)); - - val infoPart = new HttpEntity(infoString, infoPartheader); - - val file = new ClassPathResource("testfile.txt"); - val filePartheader = new HttpHeaders(); - filePartheader.setContentType(APPLICATION_OCTET_STREAM); - val filePart = new HttpEntity(file, filePartheader); - - multiParts.add("info", infoPart); - multiParts.add("file", filePart); - return multiParts; - } + @RequestMapping( + path = "/multipart/upload1/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + @SneakyThrows + public String upload1 (@PathVariable("folder") String folder, + @RequestPart MultipartFile file, + @RequestParam(value = "message", required = false) String message + ) { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } + + @RequestMapping( + path = "/multipart/upload2/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + @SneakyThrows + public String upload2 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message + ) { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } + + @RequestMapping( + path = "/multipart/upload3/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + public String upload3 (@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message + ) { + return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; + } + + @RequestMapping(path = "/multipart/upload4/{id}", method = POST) + public String upload4 (@PathVariable("id") String id, + @RequestBody Map map, + @RequestParam String userName + ) { + return userName + ':' + id + ':' + map.size(); + } + + @RequestMapping( + path = "/multipart/upload5", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE + ) + void upload5 (Dto dto) throws IOException { + assert "field 1 value".equals(dto.getField1()); + assert 42 == dto.getField2(); + + assert "Hello world".equals(new String(dto.getFile().getBytes(), UTF_8)); + } + + @RequestMapping( + path = "/multipart/download/{fileId}", + method = GET, + produces = MULTIPART_FORM_DATA_VALUE + ) + public MultiValueMap download (@PathVariable("fileId") String fileId) { + val multiParts = new LinkedMultiValueMap(); + + val infoString = "The text for file ID " + fileId + ". Testing unicode €"; + val infoPartheader = new HttpHeaders(); + infoPartheader.setContentType(new MediaType("text", "plain", UTF_8)); + + val infoPart = new HttpEntity(infoString, infoPartheader); + + val file = new ClassPathResource("testfile.txt"); + val filePartheader = new HttpHeaders(); + filePartheader.setContentType(APPLICATION_OCTET_STREAM); + val filePart = new HttpEntity(file, filePartheader); + + multiParts.add("info", infoPart); + multiParts.add("file", filePart); + return multiParts; + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 7348799c1..b0f9472e4 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -22,6 +22,7 @@ import java.util.HashMap; +import lombok.val; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,8 +31,6 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.junit4.SpringRunner; -import lombok.val; - /** * @author Tomasz Juchniewicz * @since 22.08.2016 @@ -52,7 +51,7 @@ public class SpringFormEncoderTest { private Client client; @Test - public void upload1Test() throws Exception { + public void upload1Test () throws Exception { val folder = "test_folder"; val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); val message = "message test"; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java index 0c83defd9..e99fe41f7 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java @@ -1,5 +1,23 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.feign.spring; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; @@ -10,38 +28,35 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.multipart.MultipartFile; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - @RunWith(SpringRunner.class) @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class, - properties = { - "server.port=8081", - "feign.hystrix.enabled=false" - } + webEnvironment = DEFINED_PORT, + classes = Server.class, + properties = { + "server.port=8081", + "feign.hystrix.enabled=false" + } ) public class SpringMultipartDecoderTest { - @Autowired - private DownloadClient downloadClient; + @Autowired + private DownloadClient downloadClient; - @Test - public void downloadTest() throws Exception { - MultipartFile[] downloads = downloadClient.download("123"); + @Test + public void downloadTest () throws Exception { + MultipartFile[] downloads = downloadClient.download("123"); - Assert.assertEquals(2, downloads.length); + Assert.assertEquals(2, downloads.length); - Assert.assertEquals("info", downloads[0].getName()); - MediaType infoContentType = MediaType.parseMediaType(downloads[0].getContentType()); - Assert.assertTrue(MediaType.TEXT_PLAIN.includes(infoContentType)); - Assert.assertNotNull(infoContentType.getCharset()); - Assert.assertEquals("The text for file ID 123. Testing unicode €", - IOUtils.toString(downloads[0].getInputStream(), infoContentType.getCharset().name())); - - Assert.assertEquals("testfile.txt", downloads[1].getOriginalFilename()); - Assert.assertEquals(MediaType.APPLICATION_OCTET_STREAM_VALUE, downloads[1].getContentType()); - Assert.assertEquals(14, downloads[1].getSize()); - } + Assert.assertEquals("info", downloads[0].getName()); + MediaType infoContentType = MediaType.parseMediaType(downloads[0].getContentType()); + Assert.assertTrue(MediaType.TEXT_PLAIN.includes(infoContentType)); + Assert.assertNotNull(infoContentType.getCharset()); + Assert.assertEquals("The text for file ID 123. Testing unicode €", + IOUtils.toString(downloads[0].getInputStream(), infoContentType.getCharset().name())); + Assert.assertEquals("testfile.txt", downloads[1].getOriginalFilename()); + Assert.assertEquals(MediaType.APPLICATION_OCTET_STREAM_VALUE, downloads[1].getContentType()); + Assert.assertEquals(14, downloads[1].getSize()); + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java index 77061f18e..42c6e5803 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.feign.spring.converter; @@ -5,7 +20,12 @@ import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + import feign.form.spring.converter.SpringManyMultipartFilesReader; +import lombok.val; import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; @@ -14,59 +34,53 @@ import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import lombok.val; - public class SpringManyMultipartFilesReaderTest { - private final static String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; + private static final String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; - @Test - public void readMultipartFormDataTest () throws IOException { - val multipartFilesReader = new SpringManyMultipartFilesReader(4096); - val multipartFiles = multipartFilesReader.read(MultipartFile[].class, new ValidMultipartMessage()); + @Test + public void readMultipartFormDataTest () throws IOException { + val multipartFilesReader = new SpringManyMultipartFilesReader(4096); + val multipartFiles = multipartFilesReader.read(MultipartFile[].class, new ValidMultipartMessage()); - Assert.assertEquals(2, multipartFiles.length); + Assert.assertEquals(2, multipartFiles.length); - Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, multipartFiles[0].getContentType()); - Assert.assertEquals("form-item-1", multipartFiles[0].getName()); - Assert.assertFalse(multipartFiles[0].isEmpty()); + Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, multipartFiles[0].getContentType()); + Assert.assertEquals("form-item-1", multipartFiles[0].getName()); + Assert.assertFalse(multipartFiles[0].isEmpty()); - Assert.assertEquals(MediaType.TEXT_PLAIN_VALUE, multipartFiles[1].getContentType()); - Assert.assertEquals("form-item-2-file-1", multipartFiles[1].getOriginalFilename()); - Assert.assertEquals("Plain text", IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")); - } + Assert.assertEquals(MediaType.TEXT_PLAIN_VALUE, multipartFiles[1].getContentType()); + Assert.assertEquals("form-item-2-file-1", multipartFiles[1].getOriginalFilename()); + Assert.assertEquals("Plain text", IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")); + } - public static class ValidMultipartMessage implements HttpInputMessage { + public static class ValidMultipartMessage implements HttpInputMessage { - @Override - public InputStream getBody () throws IOException { - val multipartBody = "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + - "Content-Type: application/json\r\n" + - "Content-Disposition: form-data; name=\"form-item-1\"\r\n" + - "\r\n" + - "{\"id\":1}" + "\r\n" + - "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + - "content-type: text/plain\r\n" + - "content-disposition: Form-Data; Filename=\"form-item-2-file-1\"; Name=\"form-item-2\"\r\n" + - "\r\n" + - "Plain text" + "\r\n" + - "--" + DUMMY_MULTIPART_BOUNDARY + "--\r\n"; + @Override + public InputStream getBody () throws IOException { + val multipartBody = "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + + "Content-Type: application/json\r\n" + + "Content-Disposition: form-data; name=\"form-item-1\"\r\n" + + "\r\n" + + "{\"id\":1}" + "\r\n" + + "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + + "content-type: text/plain\r\n" + + "content-disposition: Form-Data; Filename=\"form-item-2-file-1\"; Name=\"form-item-2\"\r\n" + + "\r\n" + + "Plain text" + "\r\n" + + "--" + DUMMY_MULTIPART_BOUNDARY + "--\r\n"; - return new ByteArrayInputStream(multipartBody.getBytes("US-ASCII")); - } + return new ByteArrayInputStream(multipartBody.getBytes("US-ASCII")); + } - @Override - public HttpHeaders getHeaders () { - val httpHeaders = new HttpHeaders(); - httpHeaders.put( - CONTENT_TYPE, - singletonList(MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY) - ); - return httpHeaders; - } + @Override + public HttpHeaders getHeaders () { + val httpHeaders = new HttpHeaders(); + httpHeaders.put( + CONTENT_TYPE, + singletonList(MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY) + ); + return httpHeaders; } + } } diff --git a/feign-form/src/main/java/feign/form/ContentProcessor.java b/feign-form/src/main/java/feign/form/ContentProcessor.java index f76c30e50..13c4eb971 100644 --- a/feign-form/src/main/java/feign/form/ContentProcessor.java +++ b/feign-form/src/main/java/feign/form/ContentProcessor.java @@ -20,6 +20,7 @@ import java.util.Map; import feign.RequestTemplate; +import feign.codec.EncodeException; /** * Interface for content processors. @@ -42,9 +43,9 @@ public interface ContentProcessor { * @param charset request charset from 'Content-Type' header (UTF-8 by default). * @param data reqeust data. * - * @throws Exception in case of...exception + * @throws EncodeException in case of any encode exception */ - void process (RequestTemplate template, Charset charset, Map data) throws Exception; + void process (RequestTemplate template, Charset charset, Map data) throws EncodeException; /** * Returns supported {@link ContentType} of this processor. diff --git a/feign-form/src/main/java/feign/form/ContentType.java b/feign-form/src/main/java/feign/form/ContentType.java index 442ab76c9..973e0adc8 100644 --- a/feign-form/src/main/java/feign/form/ContentType.java +++ b/feign-form/src/main/java/feign/form/ContentType.java @@ -16,7 +16,10 @@ package feign.form; +import static lombok.AccessLevel.PRIVATE; + import lombok.Getter; +import lombok.experimental.FieldDefaults; import lombok.val; /** @@ -24,14 +27,15 @@ * * @author Artem Labazin */ +@Getter +@FieldDefaults(level = PRIVATE, makeFinal = true) public enum ContentType { UNDEFINED("undefined"), URLENCODED("application/x-www-form-urlencoded"), MULTIPART("multipart/form-data"); - @Getter - private final String header; + String header; ContentType (String header) { this.header = header; diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index da42f606a..60b903e07 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -17,16 +17,13 @@ package feign.form; import static feign.form.util.CharsetUtil.UTF_8; -import static java.lang.reflect.Modifier.isFinal; -import static java.lang.reflect.Modifier.isStatic; +import static feign.form.util.PojoUtil.isUserPojo; +import static feign.form.util.PojoUtil.toMap; import static java.util.Arrays.asList; import static lombok.AccessLevel.PRIVATE; -import java.lang.reflect.Field; import java.lang.reflect.Type; import java.nio.charset.Charset; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -35,9 +32,6 @@ import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; - -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import lombok.experimental.FieldDefaults; import lombok.val; @@ -101,18 +95,14 @@ public void encode (Object object, Type bodyType, RequestTemplate template) thro if (MAP_STRING_WILDCARD.equals(bodyType)) { data = (Map) object; } else if (isUserPojo(object)) { - data = convertPojoToMap(object); + data = toMap(object); } else { delegate.encode(object, bodyType, template); return; } val charset = getCharset(contentTypeValue); - try { - processors.get(contentType).process(template, charset, data); - } catch (Exception ex) { - throw new EncodeException(ex.getMessage()); - } + processors.get(contentType).process(template, charset, data); } /** @@ -145,46 +135,7 @@ private String getContentTypeValue (Map> headers) { private Charset getCharset (String contentTypeValue) { val matcher = CHARSET_PATTERN.matcher(contentTypeValue); return matcher.find() - ? Charset.forName(matcher.group(1)) - : UTF_8; - } - - private boolean isUserPojo (Object object) { - val type = object.getClass(); - val packageName = type.getPackage().getName(); - return !packageName.startsWith("java."); - } - - @SneakyThrows - private Map convertPojoToMap (Object object) { - Map result = new HashMap(); - val type = object.getClass(); - for (val field : type.getDeclaredFields()) { - val modifiers = field.getModifiers(); - if (isFinal(modifiers) || isStatic(modifiers)) { - continue; - } - AccessController.doPrivileged(new SetAccessibleAction(field)); - - val fieldValue = field.get(object); - if (fieldValue == null) { - continue; - } - result.put(field.getName(), fieldValue); - } - return result; - } - - @RequiredArgsConstructor - @FieldDefaults(level = PRIVATE, makeFinal = true) - private class SetAccessibleAction implements PrivilegedAction { - - Field field; - - @Override - public Object run () { - field.setAccessible(true); - return null; - } + ? Charset.forName(matcher.group(1)) + : UTF_8; } } diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index b69e233bc..f717daf1d 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -19,14 +19,17 @@ import static feign.form.ContentType.MULTIPART; import static lombok.AccessLevel.PRIVATE; +import java.io.IOException; import java.nio.charset.Charset; +import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.LinkedList; -import java.util.List; import java.util.Map; import feign.Request; import feign.RequestTemplate; +import feign.codec.EncodeException; import feign.codec.Encoder; import feign.form.multipart.ByteArrayWriter; import feign.form.multipart.DelegateWriter; @@ -48,7 +51,7 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class MultipartFormContentProcessor implements ContentProcessor { - LinkedList writers; + Deque writers; Writer defaultPerocessor; @@ -70,7 +73,7 @@ public MultipartFormContentProcessor (Encoder delegate) { } @Override - public void process (RequestTemplate template, Charset charset, Map data) throws Exception { + public void process (RequestTemplate template, Charset charset, Map data) throws EncodeException { val boundary = Long.toHexString(System.currentTimeMillis()); val output = new Output(charset); @@ -87,7 +90,7 @@ public void process (RequestTemplate template, Charset charset, Mapunmodifiable list of all writers. - * - * @return writers list. - */ - public final List getWriters () { - return Collections.unmodifiableList(writers); - } - - /** - * Replaces the writer at the specified position with new element. - * - * @param index index of the element for replace. + * Returns the unmodifiable collection of all writers. * - * @param writer writer to be stored at specified position. + * @return writers collection. */ - public final void setWriter (int index, Writer writer) { - writers.set(index, writer); + public final Collection getWriters () { + return Collections.unmodifiableCollection(writers); } private Writer findApplicableWriter (Object value) { diff --git a/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java index a90e30c29..1920e9a13 100644 --- a/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java @@ -20,12 +20,13 @@ import java.net.URLEncoder; import java.nio.charset.Charset; +import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import feign.Request; import feign.RequestTemplate; - +import feign.codec.EncodeException; import lombok.SneakyThrows; import lombok.val; @@ -36,7 +37,7 @@ public class UrlencodedFormContentProcessor implements ContentProcessor { @Override - public void process (RequestTemplate template, Charset charset, Map data) throws Exception { + public void process (RequestTemplate template, Charset charset, Map data) throws EncodeException { val bodyData = new StringBuilder(); for (Entry entry : data.entrySet()) { if (bodyData.length() > 0) { @@ -53,7 +54,7 @@ public void process (RequestTemplate template, Charset charset, MapemptyList()); // reset header template.header(CONTENT_TYPE_HEADER, contentTypeValue); template.body(body); } diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index f8258cfff..221eacfe2 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -20,6 +20,7 @@ import java.net.URLConnection; +import feign.codec.EncodeException; import lombok.SneakyThrows; import lombok.val; @@ -30,7 +31,7 @@ public abstract class AbstractWriter implements Writer { @Override - public void write (Output output, String boundary, String key, Object value) throws Exception { + public void write (Output output, String boundary, String key, Object value) throws EncodeException { output.write("--").write(boundary).write(CRLF); write(output, key, value); output.write(CRLF); @@ -49,7 +50,7 @@ public void write (Output output, String boundary, String key, Object value) thr "PMD.UncommentedEmptyMethodBody", "PMD.EmptyMethodInAbstractClassShouldBeAbstract" }) - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { } /** diff --git a/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java index c3c4ffa4a..bf101b57f 100644 --- a/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java @@ -16,6 +16,8 @@ package feign.form.multipart; +import feign.codec.EncodeException; + /** * * @author Artem Labazin @@ -28,7 +30,7 @@ public boolean isApplicable (Object value) { } @Override - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { writeFileMetadata(output, key, null, null); byte[] bytes = (byte[]) value; diff --git a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java index cced4e0b9..a80f9cff3 100644 --- a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java @@ -19,6 +19,7 @@ import static lombok.AccessLevel.PRIVATE; import feign.RequestTemplate; +import feign.codec.EncodeException; import feign.codec.Encoder; import lombok.RequiredArgsConstructor; @@ -43,7 +44,7 @@ public boolean isApplicable (Object value) { } @Override - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { val fake = new RequestTemplate(); delegate.encode(value, value.getClass(), fake); val bytes = fake.requestBody().asBytes(); diff --git a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java index 5e8431cd1..9125ec8c1 100644 --- a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java @@ -16,6 +16,7 @@ package feign.form.multipart; +import feign.codec.EncodeException; import feign.form.FormData; import lombok.val; @@ -33,7 +34,7 @@ public boolean isApplicable (Object value) { } @Override - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { val formData = (FormData) value; writeFileMetadata(output, key, formData.getFileName(), formData.getContentType()); output.write(formData.getData()); diff --git a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java index 029cd4fd6..6bc16c765 100644 --- a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java @@ -20,6 +20,7 @@ import java.io.File; +import feign.codec.EncodeException; import lombok.experimental.FieldDefaults; import lombok.val; @@ -33,7 +34,7 @@ public class ManyFilesWriter extends AbstractWriter { SingleFileWriter fileWriter = new SingleFileWriter(); @Override - public void write (Output output, String boundary, String key, Object value) throws Exception { + public void write (Output output, String boundary, String key, Object value) throws EncodeException { if (value instanceof File[]) { val files = (File[]) value; for (val file : files) { diff --git a/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java b/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java index 203b2a45f..5fe497bf9 100644 --- a/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ParameterWriter.java @@ -18,6 +18,7 @@ import static feign.form.ContentProcessor.CRLF; +import feign.codec.EncodeException; import lombok.val; /** @@ -36,7 +37,7 @@ public boolean isApplicable (Object value) { } @Override - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { val string = new StringBuilder() .append("Content-Disposition: form-data; name=\"").append(key).append('"').append(CRLF) .append("Content-Type: text/plain; charset=").append(output.getCharset().name()).append(CRLF) diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java index fc4b0c578..20d952bf1 100644 --- a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -16,48 +16,40 @@ package feign.form.multipart; +import static feign.form.util.PojoUtil.isUserPojo; +import static feign.form.util.PojoUtil.toMap; import static lombok.AccessLevel.PRIVATE; -import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.List; - +import feign.codec.EncodeException; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.val; +/** + * + * @author Artem Labazin + */ @RequiredArgsConstructor @FieldDefaults(level = PRIVATE, makeFinal = true) public class PojoWriter extends AbstractWriter { - List writers; + Iterable writers; @Override public boolean isApplicable (Object object) { - val type = object.getClass(); - val packageName = type.getPackage().getName(); - return !packageName.startsWith("java."); + return isUserPojo(object); } @Override - public void write (Output output, String boundary, String key, Object object) throws Exception { - val type = object.getClass(); - for (val field : type.getDeclaredFields()) { - AccessController.doPrivileged(new SetAccessibleAction(field)); - - val value = field.get(object); - if (value == null) { - continue; - } - - val writer = findApplicableWriter(value); + public void write (Output output, String boundary, String key, Object object) throws EncodeException { + val map = toMap(object); + for (val entry : map.entrySet()) { + val writer = findApplicableWriter(entry.getValue()); if (writer == null) { continue; } - val name = field.getName(); - writer.write(output, boundary, name, value); + writer.write(output, boundary, entry.getKey(), entry.getValue()); } } @@ -69,17 +61,4 @@ private Writer findApplicableWriter (Object value) { } return null; } - - @RequiredArgsConstructor - @FieldDefaults(level = PRIVATE, makeFinal = true) - private class SetAccessibleAction implements PrivilegedAction { - - Field field; - - @Override - public Object run () { - field.setAccessible(true); - return null; - } - } } diff --git a/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java index 9366fbc85..519e4ddbe 100644 --- a/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java @@ -18,14 +18,18 @@ import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; +import feign.codec.EncodeException; +import lombok.extern.slf4j.Slf4j; import lombok.val; /** * * @author Artem Labazin */ +@Slf4j public class SingleFileWriter extends AbstractWriter { @Override @@ -34,7 +38,7 @@ public boolean isApplicable (Object value) { } @Override - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { val file = (File) value; writeFileMetadata(output, key, file.getName(), null); @@ -42,13 +46,21 @@ protected void write (Output output, String key, Object value) throws Exception try { input = new FileInputStream(file); val buf = new byte[1024]; - int length; - while ((length = input.read(buf)) > 0) { + int length = input.read(buf); + while (length > 0) { output.write(buf, 0, length); + length = input.read(buf); } + } catch (IOException ex) { + val message = String.format("Writing file's '%s' content error", file.getName()); + throw new EncodeException(message, ex); } finally { if (input != null) { - input.close(); + try { + input.close(); + } catch (IOException ex) { + log.error("Closing file '{}' error", file.getName(), ex); + } } } } diff --git a/feign-form/src/main/java/feign/form/multipart/Writer.java b/feign-form/src/main/java/feign/form/multipart/Writer.java index ad5dbc0aa..5f4e95272 100644 --- a/feign-form/src/main/java/feign/form/multipart/Writer.java +++ b/feign-form/src/main/java/feign/form/multipart/Writer.java @@ -16,6 +16,8 @@ package feign.form.multipart; +import feign.codec.EncodeException; + /** * * @author Artem Labazin @@ -29,8 +31,10 @@ public interface Writer { * @param boundary data boundary. * @param key name for piece of data. * @param value piece of data. + * + * @throws EncodeException in case of any encode exception */ - void write (Output output, String boundary, String key, Object value) throws Exception; + void write (Output output, String boundary, String key, Object value) throws EncodeException; /** * Answers on question - "could this writer properly write the value". diff --git a/feign-form/src/main/java/feign/form/util/CharsetUtil.java b/feign-form/src/main/java/feign/form/util/CharsetUtil.java index ba6979a29..1faeb6983 100644 --- a/feign-form/src/main/java/feign/form/util/CharsetUtil.java +++ b/feign-form/src/main/java/feign/form/util/CharsetUtil.java @@ -17,6 +17,7 @@ package feign.form.util; import java.nio.charset.Charset; +import java.rmi.UnexpectedException; /** * @@ -26,6 +27,7 @@ public final class CharsetUtil { public static final Charset UTF_8 = Charset.forName("UTF-8"); - private CharsetUtil () { + private CharsetUtil () throws UnexpectedException { + throw new UnexpectedException("It is not allowed to instantiate this class"); } } diff --git a/feign-form/src/main/java/feign/form/util/PojoUtil.java b/feign-form/src/main/java/feign/form/util/PojoUtil.java new file mode 100644 index 000000000..4c890f61a --- /dev/null +++ b/feign-form/src/main/java/feign/form/util/PojoUtil.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Artem Labazin + * + * 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 feign.form.util; + +import static java.lang.reflect.Modifier.isFinal; +import static java.lang.reflect.Modifier.isStatic; +import static lombok.AccessLevel.PRIVATE; + +import java.lang.reflect.Field; +import java.rmi.UnexpectedException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.experimental.FieldDefaults; +import lombok.val; + +/** + * + * @author Artem Labazin + */ +public final class PojoUtil { + + public static boolean isUserPojo (@NonNull Object object) { + val type = object.getClass(); + val packageName = type.getPackage().getName(); + return !packageName.startsWith("java."); + } + + @SneakyThrows + public static Map toMap (@NonNull Object object) { + val result = new HashMap(); + val type = object.getClass(); + val setAccessibleAction = new SetAccessibleAction(); + for (val field : type.getDeclaredFields()) { + val modifiers = field.getModifiers(); + if (isFinal(modifiers) || isStatic(modifiers)) { + continue; + } + setAccessibleAction.setField(field); + AccessController.doPrivileged(setAccessibleAction); + + val fieldValue = field.get(object); + if (fieldValue == null) { + continue; + } + result.put(field.getName(), fieldValue); + } + return result; + } + + private PojoUtil () throws UnexpectedException { + throw new UnexpectedException("It is not allowed to instantiate this class"); + } + + @Setter + @NoArgsConstructor + @FieldDefaults(level = PRIVATE) + private static class SetAccessibleAction implements PrivilegedAction { + + @Nullable + Field field; + + @Override + public Object run () { + field.setAccessible(true); + return null; + } + } +} diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index 43854e957..044372d93 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -28,12 +28,12 @@ import feign.Feign; import feign.jackson.JacksonEncoder; +import lombok.val; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; -import lombok.val; /** * @author Artem Labazin @@ -128,17 +128,6 @@ public void testMultipleFilesList () throws Exception { Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); } -// @Test - public void testMultipleManyFiles () throws Exception { - val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - Assert.assertTrue(Files.exists(path1)); - val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); - Assert.assertTrue(Files.exists(path2)); - - val stringResponse = API.uploadWithManyFiles(path1.toFile(), path2.toFile()); - Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); - } - @Test public void testUploadWithDto () throws Exception { val dto = new Dto("Artem", 11); diff --git a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java index 609d109a4..1a2fa6e27 100644 --- a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java +++ b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java @@ -16,6 +16,11 @@ package feign.form; +import static feign.Logger.Level.FULL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + import feign.Feign; import feign.Headers; import feign.Param; @@ -28,11 +33,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; -import static feign.Logger.Level.FULL; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - @RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = DEFINED_PORT, @@ -53,7 +53,7 @@ public class ByteArrayClientTest { } @Test - public void testNotTreatedAsFileUpload() { + public void testNotTreatedAsFileUpload () { byte[] bytes = "Hello World".getBytes(); val response = API.uploadByteArray(bytes); @@ -65,6 +65,6 @@ public interface CustomClient { @RequestLine("POST /upload/byte_array_parameter") @Headers("Content-Type: multipart/form-data") - Response uploadByteArray(@Param("file") byte[] bytes); + Response uploadByteArray (@Param("file") byte[] bytes); } } diff --git a/feign-form/src/test/java/feign/form/CustomClientTest.java b/feign-form/src/test/java/feign/form/CustomClientTest.java index ada7d30e2..4430551b6 100644 --- a/feign-form/src/test/java/feign/form/CustomClientTest.java +++ b/feign-form/src/test/java/feign/form/CustomClientTest.java @@ -16,22 +16,23 @@ package feign.form; -import static feign.form.ContentType.MULTIPART; import static feign.Logger.Level.FULL; +import static feign.form.ContentType.MULTIPART; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import feign.Feign; -import feign.form.multipart.ByteArrayWriter; -import feign.form.multipart.Output; import feign.Headers; -import feign.jackson.JacksonEncoder; import feign.Param; import feign.RequestLine; +import feign.codec.EncodeException; +import feign.form.multipart.ByteArrayWriter; +import feign.form.multipart.Output; +import feign.jackson.JacksonEncoder; import lombok.val; -import org.junit.runner.RunWith; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @@ -51,8 +52,7 @@ public class CustomClientTest { static { val encoder = new FormEncoder(new JacksonEncoder()); val processor = (MultipartFormContentProcessor) encoder.getContentProcessor(MULTIPART); - processor.setWriter(0, new CustomByteArrayWriter()); - + processor.addFirstWriter(new CustomByteArrayWriter()); API = Feign.builder() .encoder(encoder) @@ -72,7 +72,7 @@ public void test () { private static class CustomByteArrayWriter extends ByteArrayWriter { @Override - protected void write (Output output, String key, Object value) throws Exception { + protected void write (Output output, String key, Object value) throws EncodeException { writeFileMetadata(output, key, "popa.txt", null); val bytes = (byte[]) value; diff --git a/feign-form/src/test/java/feign/form/Dto.java b/feign-form/src/test/java/feign/form/Dto.java index 33c399e56..9429745ea 100644 --- a/feign-form/src/test/java/feign/form/Dto.java +++ b/feign-form/src/test/java/feign/form/Dto.java @@ -19,6 +19,7 @@ import static lombok.AccessLevel.PRIVATE; import java.io.Serializable; + import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index b48451faf..631eaf3e0 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.util.List; + import lombok.val; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.HttpStatus; @@ -50,9 +51,10 @@ */ @Controller @SpringBootApplication -public final class Server { +@SuppressWarnings("checkstyle:DesignForExtension") +public class Server { - @RequestMapping(value = "/form", method = POST) + @RequestMapping(path = "/form", method = POST) public ResponseEntity form (@RequestParam("key1") String key1, @RequestParam("key2") String key2 ) { @@ -62,7 +64,7 @@ public ResponseEntity form (@RequestParam("key1") String key1, return ResponseEntity.status(status).body(null); } - @RequestMapping(value = "/upload/{id}", method = POST) + @RequestMapping(path = "/upload/{id}", method = POST) @ResponseStatus(OK) public ResponseEntity upload (@PathVariable("id") Integer id, @RequestParam("public") Boolean isPublic, @@ -83,7 +85,7 @@ public ResponseEntity upload (@PathVariable("id") Integer id, return ResponseEntity.status(status).body(file.getSize()); } - @RequestMapping(value = "/upload", method = POST) + @RequestMapping(path = "/upload", method = POST) public ResponseEntity upload (@RequestParam("file") MultipartFile file) { HttpStatus status; if (file.getSize() == 0) { @@ -96,7 +98,7 @@ public ResponseEntity upload (@RequestParam("file") MultipartFile file) { return ResponseEntity.status(status).body(file.getSize()); } - @RequestMapping(value = "/upload/files", method = POST) + @RequestMapping(path = "/upload/files", method = POST) public ResponseEntity upload (@RequestParam("files") MultipartFile[] files) { HttpStatus status; if (files[0].getSize() == 0 || files[1].getSize() == 0) { @@ -110,7 +112,7 @@ public ResponseEntity upload (@RequestParam("files") MultipartFile[] files return ResponseEntity.status(status).body(files[0].getSize() + files[1].getSize()); } - @RequestMapping(value = "/json", method = POST, consumes = APPLICATION_JSON_VALUE) + @RequestMapping(path = "/json", method = POST, consumes = APPLICATION_JSON_VALUE) public ResponseEntity json (@RequestBody Dto dto) { HttpStatus status; if (!dto.getName().equals("Artem")) { @@ -123,7 +125,7 @@ public ResponseEntity json (@RequestBody Dto dto) { return ResponseEntity.status(status).body("ok"); } - @RequestMapping(value = "/query_map") + @RequestMapping("/query_map") public ResponseEntity queryMap (@RequestParam("filter") List filters) { val status = filters != null && !filters.isEmpty() ? OK @@ -131,7 +133,7 @@ public ResponseEntity queryMap (@RequestParam("filter") List fi return ResponseEntity.status(status).body(filters.size()); } - @RequestMapping(value = "/wild-card-map", method = POST, consumes = APPLICATION_FORM_URLENCODED_VALUE) + @RequestMapping(path = "/wild-card-map", method = POST, consumes = APPLICATION_FORM_URLENCODED_VALUE) public ResponseEntity wildCardMap (@RequestParam("key1") String key1, @RequestParam("key2") String key2) { val status = key1.equals(key2) ? OK @@ -139,10 +141,7 @@ public ResponseEntity wildCardMap (@RequestParam("key1") String key1, @ return ResponseEntity.status(status).body(null); } - @PostMapping( - path = "/upload/with_dto", - consumes = MULTIPART_FORM_DATA_VALUE - ) + @PostMapping(path = "/upload/with_dto", consumes = MULTIPART_FORM_DATA_VALUE) public ResponseEntity uploadWithDto (Dto dto, @RequestPart("file") MultipartFile file ) throws IOException { val status = dto != null && dto.getName().equals("Artem") @@ -151,10 +150,7 @@ public ResponseEntity uploadWithDto (Dto dto, @RequestPart("file") Multipa return ResponseEntity.status(status).body(file.getSize()); } - @PostMapping( - path = "/upload/byte_array", - consumes = MULTIPART_FORM_DATA_VALUE - ) + @PostMapping(path = "/upload/byte_array", consumes = MULTIPART_FORM_DATA_VALUE) public ResponseEntity uploadByteArray (@RequestPart("file") MultipartFile file) { val status = file != null ? OK @@ -162,10 +158,7 @@ public ResponseEntity uploadByteArray (@RequestPart("file") MultipartFil return ResponseEntity.status(status).body(file.getOriginalFilename()); } - @PostMapping( - path = "/upload/byte_array_parameter", - consumes = MULTIPART_FORM_DATA_VALUE - ) + @PostMapping(path = "/upload/byte_array_parameter", consumes = MULTIPART_FORM_DATA_VALUE) // We just want the request because when there's a filename part of the Content-Disposition header spring // will treat it as a file (available through getFile()) and when it doesn't have the filename part it's // available in the parameter (getParameter()) @@ -176,10 +169,7 @@ public ResponseEntity uploadByteArrayParameter (MultipartHttpServletRequ return ResponseEntity.status(status).build(); } - @PostMapping( - path = "/upload/unknown_type", - consumes = MULTIPART_FORM_DATA_VALUE - ) + @PostMapping(path = "/upload/unknown_type", consumes = MULTIPART_FORM_DATA_VALUE) public ResponseEntity uploadUnknownType (@RequestPart("file") MultipartFile file) { val status = file != null ? OK @@ -187,10 +177,7 @@ public ResponseEntity uploadUnknownType (@RequestPart("file") MultipartF return ResponseEntity.status(status).body(file.getContentType()); } - @PostMapping( - path = "/upload/form_data", - consumes = MULTIPART_FORM_DATA_VALUE - ) + @PostMapping(path = "/upload/form_data", consumes = MULTIPART_FORM_DATA_VALUE) public ResponseEntity uploadFormData (@RequestPart("file") MultipartFile file) { val status = file != null ? OK diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index 1894c7d45..d9a7d1c6e 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -29,8 +29,8 @@ import feign.Response; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.runner.RunWith; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @@ -41,11 +41,11 @@ ) public class WildCardMapTest { - private static FormUrlEncodedApi API; + private static FormUrlEncodedApi api; @BeforeClass public static void configureClient () { - API = Feign.builder() + api = Feign.builder() .encoder(new FormEncoder()) .logger(new Logger.JavaLogger().appendToFile("log.txt")) .logLevel(FULL) @@ -63,7 +63,7 @@ public void testOk () { put("key2", "1"); } }; - Response response = API.wildCardMap(param); + Response response = api.wildCardMap(param); Assert.assertEquals(200, response.status()); } @@ -79,7 +79,7 @@ public void testBadRequest () { put("key2", "2"); } }; - Response response = API.wildCardMap(param); + Response response = api.wildCardMap(param); Assert.assertEquals(418, response.status()); } diff --git a/pom.xml b/pom.xml index c630d637d..444d3c43f 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,12 @@ limitations under the License. provided + + org.slf4j + slf4j-api + 1.7.25 + + io.github.openfeign feign-core From 54e4370027f8087e6dabc10f6743de961ff664d6 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 24 Dec 2018 14:53:43 +0300 Subject: [PATCH 68/93] Some fixes --- .../src/main/java/feign/form/spring/SpringFormEncoder.java | 2 +- .../src/main/java/feign/form/multipart/AbstractWriter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java index bb26c7045..be6c96d9c 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java @@ -33,7 +33,7 @@ /** * Adds support for {@link MultipartFile} type to {@link FormEncoder}. * - * @author Tomasz Juchniewicz + * @author Tomasz Juchniewicz <tjuchniewicz@gmail.com> * @since 14.09.2016 */ public class SpringFormEncoder extends FormEncoder { diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index 221eacfe2..2265d7c44 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -44,7 +44,7 @@ public void write (Output output, String boundary, String key, Object value) thr * @param key name for piece of data. * @param value piece of data. * - * @throws Exception in case of write errors + * @throws EncodeException in case of write errors */ @SuppressWarnings({ "PMD.UncommentedEmptyMethodBody", From 53e9f4d7d14f8f1d28da7218b2395afe1c29cc1a Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 24 Dec 2018 15:23:50 +0300 Subject: [PATCH 69/93] Fix readme --- README.md | 204 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 112 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 08dfbd25e..97edd002c 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,11 @@ Include the dependency to your project's pom.xml file: ```xml - ... - - io.github.openfeign.form - feign-form - 3.5.0 - - ... + + io.github.openfeign.form + feign-form + 3.5.0 + ``` @@ -27,16 +25,16 @@ Add `FormEncoder` to your `Feign.Builder` like so: ```java SomeApi github = Feign.builder() - .encoder(new FormEncoder()) - .target(SomeApi.class, "http://api.some.org"); + .encoder(new FormEncoder()) + .target(SomeApi.class, "http://api.some.org"); ``` Moreover, you can decorate the existing encoder, for example JsonEncoder like this: ```java SomeApi github = Feign.builder() - .encoder(new FormEncoder(new JacksonEncoder())) - .target(SomeApi.class, "http://api.some.org"); + .encoder(new FormEncoder(new JacksonEncoder())) + .target(SomeApi.class, "http://api.some.org"); ``` And use them together: @@ -44,14 +42,13 @@ And use them together: ```java interface SomeApi { - @RequestLine("POST /json") - @Headers("Content-Type: application/json") - void json (Dto dto); - - @RequestLine("POST /form") - @Headers("Content-Type: application/x-www-form-urlencoded") - void from (@Param("field1") String field1, @Param("field2") String field2); + @RequestLine("POST /json") + @Headers("Content-Type: application/json") + void json (Dto dto); + @RequestLine("POST /form") + @Headers("Content-Type: application/x-www-form-urlencoded") + void from (@Param("field1") String field1, @Param("field2") String field2); } ``` @@ -62,14 +59,21 @@ You can specify two types of encoding forms by `Content-Type` header. ```java interface SomeApi { - ... + @RequestLine("POST /authorization") + @Headers("Content-Type: application/x-www-form-urlencoded") + void authorization (@Param("email") String email, @Param("password") String password); + + // Group all parameters within a POJO + @RequestLine("POST /user") + @Headers("Content-Type: application/x-www-form-urlencoded") + void addUser (User user); - @RequestLine("POST /authorization") - @Headers("Content-Type: application/x-www-form-urlencoded") - void authorization (@Param("email") String email, @Param("password") String password); + class User { - ... + Integer id; + String name; + } } ``` @@ -78,38 +82,47 @@ interface SomeApi { ```java interface SomeApi { - ... + // File parameter + @RequestLine("POST /send_photo") + @Headers("Content-Type: multipart/form-data") + void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") File photo); + + // byte[] parameter + @RequestLine("POST /send_photo") + @Headers("Content-Type: multipart/form-data") + void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") byte[] photo); - // File parameter - @RequestLine("POST /send_photo") - @Headers("Content-Type: multipart/form-data") - void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") File photo); + // FormData parameter + @RequestLine("POST /send_photo") + @Headers("Content-Type: multipart/form-data") + void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") FormData photo); - // byte[] parameter - @RequestLine("POST /send_photo") - @Headers("Content-Type: multipart/form-data") - void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") byte[] photo); + // Group all parameters within a POJO + @RequestLine("POST /send_photo") + @Headers("Content-Type: multipart/form-data") + void sendPhoto (MyPojo pojo); - // FormData parameter - @RequestLine("POST /send_photo") - @Headers("Content-Type: multipart/form-data") - void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") FormData photo); - ... + class MyPojo { + Boolean isPublic; + + File photo; + } } ``` In the example above, the `sendPhoto` method uses the `photo` parameter using three different supported types. -* `File` will use the File's extension to detect the `Content-Type`. -* `byte[]` will use `application/octet-stream` as `Content-Type`. -* `FormData` will use the `FormData`'s `Content-Type` and `fileName`. +* `File` will use the File's extension to detect the `Content-Type`; +* `byte[]` will use `application/octet-stream` as `Content-Type`; +* `FormData` will use the `FormData`'s `Content-Type` and `fileName`; +* Client's custom POJO for grouping parameters (including types above). `FormData` is custom object that wraps a `byte[]` and defines a `Content-Type` and `fileName` like this: ```java - FormData formData = new FormData("image/png", "filename.png", myDataAsByteArray); - someApi.sendPhoto(true, formData); + FormData formData = new FormData("image/png", "filename.png", myDataAsByteArray); + someApi.sendPhoto(true, formData); ``` ### Spring MultipartFile and Spring Cloud Netflix @FeignClient support @@ -120,51 +133,55 @@ Include the dependencies to your project's pom.xml file: ```xml - ... - - io.github.openfeign.form - feign-form - 3.5.0 - - - io.github.openfeign.form - feign-form-spring - 3.5.0 - - ... + + io.github.openfeign.form + feign-form + 3.5.0 + + + io.github.openfeign.form + feign-form-spring + 3.5.0 + ``` ```java -@FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class) +@FeignClient( + name = "file-upload-service", + configuration = FileUploadServiceClient.MultipartSupportConfig.class +) public interface FileUploadServiceClient extends IFileUploadServiceClient { - public class MultipartSupportConfig { + public class MultipartSupportConfig { - @Autowired - private ObjectFactory messageConverters; + @Autowired + private ObjectFactory messageConverters; - @Bean - public Encoder feignFormEncoder() { - return new SpringFormEncoder(new SpringEncoder(messageConverters)); - } + @Bean + public Encoder feignFormEncoder () { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); } + } } ``` Or, if you don't need Spring's standard encoder: ```java -@FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class) +@FeignClient( + name = "file-upload-service", + configuration = FileUploadServiceClient.MultipartSupportConfig.class +) public interface FileUploadServiceClient extends IFileUploadServiceClient { - public class MultipartSupportConfig { + public class MultipartSupportConfig { - @Bean - public Encoder feignFormEncoder() { - return new SpringFormEncoder(); - } + @Bean + public Encoder feignFormEncoder () { + return new SpringFormEncoder(); } + } } ``` @@ -174,38 +191,41 @@ To use this feature, include SpringManyMultipartFilesReader in the list of messa ```java @FeignClient( - name = "${feign.name}", - url = "${feign.url}" - configuration = DownloadClient.ClientConfiguration.class) + name = "${feign.name}", + url = "${feign.url}" + configuration = DownloadClient.ClientConfiguration.class +) public interface DownloadClient { - @RequestMapping( - value = "/multipart/download/{fileId}", - method = GET) - MultipartFile[] download(@PathVariable("fileId") String fileId); + @RequestMapping("/multipart/download/{fileId}") + MultipartFile[] download(@PathVariable("fileId") String fileId); + + class ClientConfiguration { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public Decoder feignDecoder () { + List> springConverters = + messageConverters.getObject().getConverters(); - class ClientConfiguration { + List> decoderConverters = + new ArrayList>(springConverters.size() + 1; - @Autowired - private ObjectFactory messageConverters; + decoderConverters.addAll(springConverters); + decoderConverters.add(new SpringManyMultipartFilesReader(4096)); - @Bean - public Decoder feignDecoder () { - final List> springConverters = messageConverters.getObject().getConverters(); - final List> decoderConverters - = new ArrayList>(springConverters.size() + 1); + HttpMessageConverters httpMessageConverters = new HttpMessageConverters(decoderConverters); - decoderConverters.addAll(springConverters); - decoderConverters.add(new SpringManyMultipartFilesReader(4096)); - final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(decoderConverters); + return new SpringDecoder(new ObjectFactory() { - return new SpringDecoder(new ObjectFactory() { - @Override - public HttpMessageConverters getObject() { - return httpMessageConverters; - } - }); + @Override + public HttpMessageConverters getObject() { + return httpMessageConverters; } + }); } + } } ``` From e1caf57a8d0be7808f74940444c8cb9043be821c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=AF=9A?= <188727774@qq.com> Date: Mon, 7 Jan 2019 22:40:36 +0800 Subject: [PATCH 70/93] =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=B0=91=E4=B8=AA=E5=8F=B3=E6=8B=AC=E5=8F=B7=20(#57)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97edd002c..f43067cf5 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ public interface DownloadClient { messageConverters.getObject().getConverters(); List> decoderConverters = - new ArrayList>(springConverters.size() + 1; + new ArrayList>(springConverters.size() + 1); decoderConverters.addAll(springConverters); decoderConverters.add(new SpringManyMultipartFilesReader(4096)); From 81a614553b18654c660e733f8543b5946ee5affa Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sun, 3 Mar 2019 17:59:53 +0300 Subject: [PATCH 71/93] Issue60 (#62) Add repeatable parameters encoding --- .codestyle/checkstyle.xml | 3 +- .codestyle/findbugs.xml | 3 +- .codestyle/license_for_check.txt | 2 +- .codestyle/pmd.xml | 3 +- LICENSE | 2 +- README.md | 8 +-- feign-form-spring/pom.xml | 34 +++------- .../feign/form/spring/SpringFormEncoder.java | 2 +- .../SpringManyMultipartFilesWriter.java | 2 +- .../SpringSingleMultipartFileWriter.java | 2 +- .../converter/ByteArrayMultipartFile.java | 2 +- .../spring/converter/IgnoreKeyCaseMap.java | 2 +- .../SpringManyMultipartFilesReader.java | 2 +- .../java/feign/form/feign/spring/Client.java | 2 +- .../form/feign/spring/DownloadClient.java | 2 +- .../java/feign/form/feign/spring/Dto.java | 2 +- .../java/feign/form/feign/spring/Server.java | 2 +- .../feign/spring/SpringFormEncoderTest.java | 2 +- .../spring/SpringMultipartDecoderTest.java | 2 +- .../SpringManyMultipartFilesReaderTest.java | 2 +- feign-form/pom.xml | 20 +----- .../java/feign/form/ContentProcessor.java | 2 +- .../src/main/java/feign/form/ContentType.java | 2 +- .../src/main/java/feign/form/FormData.java | 2 +- .../src/main/java/feign/form/FormEncoder.java | 2 +- .../form/MultipartFormContentProcessor.java | 11 +++- .../form/UrlencodedFormContentProcessor.java | 63 +++++++++++++++++-- .../feign/form/multipart/AbstractWriter.java | 2 +- .../feign/form/multipart/ByteArrayWriter.java | 2 +- .../feign/form/multipart/DelegateWriter.java | 4 +- .../feign/form/multipart/FormDataWriter.java | 2 +- .../feign/form/multipart/ManyFilesWriter.java | 33 +++++----- .../form/multipart/ManyParametersWriter.java | 62 ++++++++++++++++++ .../java/feign/form/multipart/Output.java | 2 +- .../java/feign/form/multipart/PojoWriter.java | 2 +- .../form/multipart/SingleFileWriter.java | 2 +- ...Writer.java => SingleParameterWriter.java} | 10 ++- .../java/feign/form/multipart/Writer.java | 2 +- .../java/feign/form/util/CharsetUtil.java | 2 +- .../main/java/feign/form/util/PojoUtil.java | 2 +- .../test/java/feign/form/BasicClientTest.java | 18 +++++- .../java/feign/form/ByteArrayClientTest.java | 2 +- .../java/feign/form/CustomClientTest.java | 2 +- feign-form/src/test/java/feign/form/Dto.java | 2 +- .../src/test/java/feign/form/Server.java | 40 +++++++++++- .../src/test/java/feign/form/TestClient.java | 11 +++- .../test/java/feign/form/WildCardMapTest.java | 2 +- pom.xml | 55 +++++++++------- 48 files changed, 296 insertions(+), 146 deletions(-) create mode 100644 feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java rename feign-form/src/main/java/feign/form/multipart/{ParameterWriter.java => SingleParameterWriter.java} (87%) diff --git a/.codestyle/checkstyle.xml b/.codestyle/checkstyle.xml index c25fe200c..3784bf17c 100644 --- a/.codestyle/checkstyle.xml +++ b/.codestyle/checkstyle.xml @@ -4,7 +4,7 @@ "http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd"> - + org.apache.maven.plugins maven-gpg-plugin 1.6 From 1b3f2cf3b60eb6b105c6924c5c96b652e1cc5cab Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 29 Mar 2019 23:43:19 +0300 Subject: [PATCH 78/93] fix checkstyle --- .../java/feign/form/feign/spring/Client.java | 7 ------ .../java/feign/form/feign/spring/Server.java | 22 +++++++++---------- .../feign/spring/SpringFormEncoderTest.java | 9 -------- 3 files changed, 11 insertions(+), 27 deletions(-) diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index e985958d2..d116faca8 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -108,13 +108,6 @@ String upload4 (@PathVariable("id") String id, ) String upload6Collection (@RequestPart List files); -// @RequestMapping( -// path = "/multipart/upload6", -// method = POST, -// consumes = MULTIPART_FORM_DATA_VALUE -// ) -// String upload6Arguments (@RequestPart("popa1") MultipartFile file1, @RequestPart("popa2") MultipartFile file2); - class ClientConfiguration { @Autowired diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index eabd3acd8..953a0b187 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -121,17 +121,17 @@ void upload5 (Dto dto) throws IOException { method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) - public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa1, - @RequestParam("popa2") MultipartFile popa2 - ) throws Exception { - HttpStatus status = I_AM_A_TEAPOT; - String result = ""; - if (popa1 != null && popa2 != null) { - status = OK; - result = new String(popa1.getBytes()) + new String(popa2.getBytes()); - } - return ResponseEntity.status(status).body(result); - } + public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa1, + @RequestParam("popa2") MultipartFile popa2 + ) throws Exception { + HttpStatus status = I_AM_A_TEAPOT; + String result = ""; + if (popa1 != null && popa2 != null) { + status = OK; + result = new String(popa1.getBytes()) + new String(popa2.getBytes()); + } + return ResponseEntity.status(status).body(result); + } @RequestMapping( path = "/multipart/download/{fileId}", diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 50f8d6abe..1736cfa26 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -133,13 +133,4 @@ public void upload6CollectionTest () throws Exception { val response = client.upload6Collection(list); Assert.assertEquals("Hello world", response); } - -// @Test -// public void upload6ArgumentsTest () throws Exception { -// val file1 = new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)); -// val file2 = new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8)); -// -// val response = client.upload6Arguments(file1, file2); -// Assert.assertEquals("Hello world", response); -// } } From 3bf02f6bac8158d3a6646592694462c26884b0fc Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 29 Mar 2019 23:47:53 +0300 Subject: [PATCH 79/93] update version --- README.md | 8 ++++---- feign-form-spring/pom.xml | 2 +- feign-form/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 98a3bd491..3f1fc5fd4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Include the dependency to your app: io.github.openfeign.form feign-form - 3.7.0 + 3.8.0 ... @@ -27,7 +27,7 @@ Include the dependency to your app: **Gradle**: ```groovy -compile 'io.github.openfeign.form:feign-form:3.7.0' +compile 'io.github.openfeign.form:feign-form:3.8.0' ``` ## Requirements @@ -162,12 +162,12 @@ Include the dependencies to your project's pom.xml file: io.github.openfeign.form feign-form - 3.7.0 + 3.8.0 io.github.openfeign.form feign-form-spring - 3.7.0 + 3.8.0 ``` diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index dbac1a9ef..17a9ff9f7 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -28,7 +28,7 @@ limitations under the License. io.github.openfeign.form parent - 3.7.0 + 3.8.0 Open Feign Forms Extension for Spring diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 9228f8179..7e1d0f8b9 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -28,7 +28,7 @@ limitations under the License. io.github.openfeign.form parent - 3.7.0 + 3.8.0 Open Feign Forms Core diff --git a/pom.xml b/pom.xml index 3c0b02254..d03856210 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ limitations under the License. io.github.openfeign.form parent - 3.7.0 + 3.8.0 pom @@ -62,7 +62,7 @@ limitations under the License. https://github.com/OpenFeign/feign-form scm:git:https://github.com/OpenFeign/feign-form.git scm:git:https://github.com/OpenFeign/feign-form.git - 3.7.0 + 3.8.0 From 6eebea7cad88b4e90e95709d6d98b160ad137550 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Fri, 29 Mar 2019 23:50:03 +0300 Subject: [PATCH 80/93] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3f1fc5fd4..6aec9264e 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ interface SomeApi { class MyPojo { + @FormProperty("is_public") Boolean isPublic; File photo; From 6cf7435855d1dc2171cfa6e93ee58cafcf729d0a Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Sat, 30 Mar 2019 00:04:51 +0300 Subject: [PATCH 81/93] fix readme --- README.md | 2 +- feign-form/src/test/java/feign/form/Server.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6aec9264e..d68c34322 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ public interface FileUploadServiceClient extends IFileUploadServiceClient { } ``` -Thanks to [tf-haotri-pham](https://github.com/tf-haotri-pham) for his featur, which makes use of Apache commons-fileupload library, which handles the parsing of the multipart response. The body data parts are held as byte arrays in memory. +Thanks to [tf-haotri-pham](https://github.com/tf-haotri-pham) for his feature, which makes use of Apache commons-fileupload library, which handles the parsing of the multipart response. The body data parts are held as byte arrays in memory. To use this feature, include SpringManyMultipartFilesReader in the list of message converters for the Decoder and have the Feign client return an array of MultipartFile: diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 603f8e73f..d6c5a3657 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -221,7 +221,7 @@ public ResponseEntity submitRepeatableFormParam (@RequestParam("names") @PostMapping(path = "/form-data", consumes = APPLICATION_FORM_URLENCODED_VALUE) public ResponseEntity submitPostData (@RequestParam("f_name") String firstName, - @RequestParam("age") Integer age) { + @RequestParam("age") Integer age) { val response = new StringBuilder(); if (firstName != null && age != null) { response @@ -235,5 +235,4 @@ public ResponseEntity submitPostData (@RequestParam("f_name") String fir return ResponseEntity.status(status).body(response.toString()); } - } From 98e51d187f1b7acba3b39d1131371b761d87259b Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Thu, 18 Apr 2019 15:33:20 +0300 Subject: [PATCH 82/93] fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d68c34322..7c283b96e 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ compile 'io.github.openfeign.form:feign-form:3.8.0' The `feign-form` extension depend on `OpenFeign` and its *concrete* versions: - all `feign-form` releases before **3.5.0** works with `OpenFeign` **9.\*** versions; -- starting from `feign-form`'s version **3.5.0**, the module works with `OpenFeign` **10.\*** versions. +- starting from `feign-form`'s version **3.5.0**, the module works with `OpenFeign` **10.1.0** versions and greater. > **IMPORTANT:** there is no backward compatibility and no any gurantee that the `feign-form`'s versions after **3.5.0** work with `OpenFeign` before **10.\***. `OpenFeign` was refactored in 10th release, so the best approach - use the freshest `OpenFeign` and `feign-form` versions. From c8f07a63b8cbcd2f2209189280aefc01260e8df1 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Mon, 24 Jun 2024 22:55:18 +0200 Subject: [PATCH 83/93] update copyright --- .codestyle/checkstyle.xml | 2 +- .codestyle/findbugs.xml | 2 +- .codestyle/license_for_check.txt | 2 +- .codestyle/pmd.xml | 2 +- LICENSE | 2 +- feign-form-spring/pom.xml | 2 +- .../src/main/java/feign/form/spring/SpringFormEncoder.java | 2 +- .../java/feign/form/spring/SpringManyMultipartFilesWriter.java | 2 +- .../java/feign/form/spring/SpringSingleMultipartFileWriter.java | 2 +- .../feign/form/spring/converter/ByteArrayMultipartFile.java | 2 +- .../main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java | 2 +- .../form/spring/converter/SpringManyMultipartFilesReader.java | 2 +- .../src/test/java/feign/form/feign/spring/Client.java | 2 +- .../src/test/java/feign/form/feign/spring/DownloadClient.java | 2 +- .../src/test/java/feign/form/feign/spring/Dto.java | 2 +- .../src/test/java/feign/form/feign/spring/Server.java | 2 +- .../java/feign/form/feign/spring/SpringFormEncoderTest.java | 2 +- .../feign/form/feign/spring/SpringMultipartDecoderTest.java | 2 +- .../spring/converter/SpringManyMultipartFilesReaderTest.java | 2 +- feign-form/pom.xml | 2 +- feign-form/src/main/java/feign/form/ContentProcessor.java | 2 +- feign-form/src/main/java/feign/form/ContentType.java | 2 +- feign-form/src/main/java/feign/form/FormData.java | 2 +- feign-form/src/main/java/feign/form/FormEncoder.java | 2 +- feign-form/src/main/java/feign/form/FormProperty.java | 2 +- .../src/main/java/feign/form/MultipartFormContentProcessor.java | 2 +- .../main/java/feign/form/UrlencodedFormContentProcessor.java | 2 +- .../src/main/java/feign/form/multipart/AbstractWriter.java | 2 +- .../src/main/java/feign/form/multipart/ByteArrayWriter.java | 2 +- .../src/main/java/feign/form/multipart/DelegateWriter.java | 2 +- .../src/main/java/feign/form/multipart/FormDataWriter.java | 2 +- .../src/main/java/feign/form/multipart/ManyFilesWriter.java | 2 +- .../main/java/feign/form/multipart/ManyParametersWriter.java | 2 +- feign-form/src/main/java/feign/form/multipart/Output.java | 2 +- feign-form/src/main/java/feign/form/multipart/PojoWriter.java | 2 +- .../src/main/java/feign/form/multipart/SingleFileWriter.java | 2 +- .../main/java/feign/form/multipart/SingleParameterWriter.java | 2 +- feign-form/src/main/java/feign/form/multipart/Writer.java | 2 +- feign-form/src/main/java/feign/form/util/CharsetUtil.java | 2 +- feign-form/src/main/java/feign/form/util/PojoUtil.java | 2 +- feign-form/src/test/java/feign/form/BasicClientTest.java | 2 +- feign-form/src/test/java/feign/form/ByteArrayClientTest.java | 2 +- feign-form/src/test/java/feign/form/CustomClientTest.java | 2 +- feign-form/src/test/java/feign/form/Dto.java | 2 +- feign-form/src/test/java/feign/form/FormDto.java | 2 +- feign-form/src/test/java/feign/form/FormPropertyTest.java | 2 +- feign-form/src/test/java/feign/form/Server.java | 2 +- feign-form/src/test/java/feign/form/TestClient.java | 2 +- feign-form/src/test/java/feign/form/WildCardMapTest.java | 2 +- pom.xml | 2 +- 50 files changed, 50 insertions(+), 50 deletions(-) diff --git a/.codestyle/checkstyle.xml b/.codestyle/checkstyle.xml index 3784bf17c..14b58d2a1 100644 --- a/.codestyle/checkstyle.xml +++ b/.codestyle/checkstyle.xml @@ -4,7 +4,7 @@ "http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd"> - + @@ -169,8 +171,9 @@ limitations under the License. - + value="STATIC###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS"/> + @@ -182,9 +185,6 @@ limitations under the License. - - - @@ -346,7 +346,6 @@ limitations under the License. - @@ -377,7 +376,7 @@ limitations under the License. - + diff --git a/.codestyle/pmd.xml b/.codestyle/pmd.xml index bdd4ed3a4..fd3031aac 100644 --- a/.codestyle/pmd.xml +++ b/.codestyle/pmd.xml @@ -24,16 +24,15 @@ limitations under the License. PMD Rules - + + - + - - @@ -48,6 +47,7 @@ limitations under the License. + @@ -55,12 +55,18 @@ limitations under the License. + + + + + + @@ -68,18 +74,14 @@ limitations under the License. - - - + + - + - - - diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index fa4f7b499..776860764 100755 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -1,110 +1,98 @@ /* -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 + * 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. + */ - http://www.apache.org/licenses/LICENSE-2.0 + import java.io.IOException; + import java.io.InputStream; + import java.net.Authenticator; + import java.net.PasswordAuthentication; + import java.net.URL; + import java.nio.file.Files; + import java.nio.file.Path; + import java.nio.file.Paths; + import java.nio.file.StandardCopyOption; -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. -*/ + public final class MavenWrapperDownloader + { + private static final String WRAPPER_VERSION = "@@project.version@@"; -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; + private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); -public class MavenWrapperDownloader { + public static void main( String[] args ) + { + log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = - "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + if ( args.length != 2 ) + { + System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); + System.exit( 1 ); + } - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; + try + { + log( " - Downloader started" ); + final URL wrapperUrl = new URL( args[0] ); + final String jarPath = args[1].replace( "..", "" ); // Sanitize path + final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); + downloadFileFromURL( wrapperUrl, wrapperJarPath ); + log( "Done" ); + } + catch ( IOException e ) + { + System.err.println( "- Error downloading: " + e.getMessage() ); + if ( VERBOSE ) + { + e.printStackTrace(); + } + System.exit( 1 ); + } + } - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; + private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) + throws IOException + { + log( " - Downloading to: " + wrapperJarPath ); + if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) + { + final String username = System.getenv( "MVNW_USERNAME" ); + final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); + Authenticator.setDefault( new Authenticator() + { + @Override + protected PasswordAuthentication getPasswordAuthentication() + { + return new PasswordAuthentication( username, password ); + } + } ); + } + try ( InputStream inStream = wrapperUrl.openStream() ) + { + Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); + } + log( " - Downloader complete" ); + } - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + private static void log( String msg ) + { + if ( VERBOSE ) + { + System.out.println( msg ); + } + } - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: : " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} + } diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index cd0d451cc..ca5ab4bab 100755 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,18 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip +# 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 +# +# https://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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/deps.tree b/deps.tree new file mode 100644 index 000000000..4a5dfbd3a --- /dev/null +++ b/deps.tree @@ -0,0 +1,442 @@ +[INFO] Scanning for projects... +[INFO] Inspecting build with total of 3 modules... +[INFO] Installing Nexus Staging features: +[INFO] ... total of 3 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Build Order: +[INFO] +[INFO] Open Feign Forms Parent [pom] +[INFO] Open Feign Forms Core [jar] +[INFO] Open Feign Forms Extension for Spring [jar] +[INFO] +[INFO] ------------------< io.github.openfeign.form:parent >------------------- +[INFO] Building Open Feign Forms Parent 4.0.0 [1/3] +[INFO] from pom.xml +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] --- dependency:3.7.0:tree (default-cli) @ parent --- +[INFO] io.github.openfeign.form:parent:pom:4.0.0 +[INFO] +- org.projectlombok:lombok:jar:1.18.32:provided +[INFO] +- org.slf4j:slf4j-api:jar:2.0.13:compile +[INFO] +- io.github.openfeign:feign-core:jar:13.3:provided +[INFO] +- io.github.openfeign:feign-jackson:jar:13.3:test +[INFO] | +- (io.github.openfeign:feign-core:jar:13.3:test - omitted for duplicate) +[INFO] | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:test +[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:test +[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:test +[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:test +[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.18:test +[INFO] | | +- org.springframework.boot:spring-boot:jar:2.7.18:test +[INFO] | | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-context:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test +[INFO] | | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.18:test +[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.12:test +[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.2.12:test +[INFO] | | | | \- (org.slf4j:slf4j-api:jar:1.7.32:test - omitted for conflict with 2.0.13) +[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:test +[INFO] | | | | +- (org.slf4j:slf4j-api:jar:1.7.35:test - omitted for conflict with 2.0.13) +[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:test +[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:test +[INFO] | | | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) +[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test +[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- org.yaml:snakeyaml:jar:1.30:test +[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.18:test +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.5:test +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.5:test +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.5:test +[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.18:test +[INFO] | | +- (jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test - omitted for duplicate) +[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test +[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.83:test +[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.83:test +[INFO] | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-web:jar:5.3.31:test +[INFO] | | +- org.springframework:spring-beans:jar:5.3.31:test +[INFO] | | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- org.springframework:spring-webmvc:jar:5.3.31:test +[INFO] | +- org.springframework:spring-aop:jar:5.3.31:test +[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-context:jar:5.3.31:test +[INFO] | | +- (org.springframework:spring-aop:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-expression:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-expression:jar:5.3.31:test +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.18:test +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.18:test +[INFO] | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.18:test +[INFO] | | +- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-test:jar:2.7.18:test - omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test +[INFO] | | +- net.minidev:json-smart:jar:2.4.7:test +[INFO] | | | \- net.minidev:accessors-smart:jar:2.4.7:test +[INFO] | | | \- org.ow2.asm:asm:jar:9.1:test +[INFO] | | \- (org.slf4j:slf4j-api:jar:1.7.33:test - omitted for conflict with 2.0.13) +[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test +[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test +[INFO] | +- (org.assertj:assertj-core:jar:3.22.0:test - omitted for conflict with 3.26.0) +[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test +[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test +[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | | +- (org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | | \- (org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | +- (org.mockito:mockito-core:jar:4.5.1:test - omitted for conflict with 5.12.0) +[INFO] | +- (org.mockito:mockito-junit-jupiter:jar:4.5.1:test - omitted for conflict with 5.12.0) +[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.1:test +[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test +[INFO] | +- org.springframework:spring-core:jar:5.3.31:test +[INFO] | | \- org.springframework:spring-jcl:jar:5.3.31:test +[INFO] | +- org.springframework:spring-test:jar:5.3.31:test +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.1:test +[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test - omitted for duplicate) +[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test (scope not updated to test) +[INFO] | +- org.junit.platform:junit-platform-engine:jar:1.10.2:test +[INFO] | | +- org.opentest4j:opentest4j:jar:1.3.0:test +[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test +[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test +[INFO] | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate) +[INFO] | | +- (org.junit.platform:junit-platform-commons:jar:1.10.2:test - omitted for duplicate) +[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | \- org.apiguardian:apiguardian-api:jar:1.1.2:test +[INFO] +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test (scope not updated to test) +[INFO] | +- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) +[INFO] | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] +- org.assertj:assertj-core:jar:3.26.0:test (scope not updated to test) +[INFO] | \- net.bytebuddy:byte-buddy:jar:1.14.16:test +[INFO] +- org.mockito:mockito-core:jar:5.12.0:test (scope not updated to test) +[INFO] | +- (net.bytebuddy:byte-buddy:jar:1.14.15:test - omitted for conflict with 1.14.16) +[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.14.15:test +[INFO] | \- org.objenesis:objenesis:jar:3.3:test +[INFO] +- org.mockito:mockito-junit-jupiter:jar:5.12.0:test (scope not updated to test) +[INFO] | +- (org.mockito:mockito-core:jar:5.12.0:test - omitted for duplicate) +[INFO] | \- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) +[INFO] +- net.jcip:jcip-annotations:jar:1.0:provided +[INFO] \- com.github.spotbugs:spotbugs-annotations:jar:4.8.6:provided +[INFO] \- com.google.code.findbugs:jsr305:jar:3.0.2:provided +[INFO] +[INFO] ----------------< io.github.openfeign.form:feign-form >----------------- +[INFO] Building Open Feign Forms Core 4.0.0 [2/3] +[INFO] from feign-form/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- dependency:3.7.0:tree (default-cli) @ feign-form --- +[INFO] io.github.openfeign.form:feign-form:jar:4.0.0 +[INFO] +- org.projectlombok:lombok:jar:1.18.32:provided +[INFO] +- org.slf4j:slf4j-api:jar:2.0.13:compile +[INFO] +- io.github.openfeign:feign-core:jar:13.3:provided +[INFO] +- io.github.openfeign:feign-jackson:jar:13.3:test +[INFO] | +- (io.github.openfeign:feign-core:jar:13.3:test - omitted for duplicate) +[INFO] | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:test +[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:test +[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:test +[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:test +[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.18:test +[INFO] | | +- org.springframework.boot:spring-boot:jar:2.7.18:test +[INFO] | | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-context:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test +[INFO] | | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.18:test +[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.12:test +[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.2.12:test +[INFO] | | | | \- (org.slf4j:slf4j-api:jar:1.7.32:test - omitted for conflict with 2.0.13) +[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:test +[INFO] | | | | +- (org.slf4j:slf4j-api:jar:1.7.35:test - omitted for conflict with 2.0.13) +[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:test +[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:test +[INFO] | | | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) +[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test +[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- org.yaml:snakeyaml:jar:1.30:test +[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.18:test +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.5:test +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.5:test +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.5:test +[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.18:test +[INFO] | | +- (jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test - omitted for duplicate) +[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test +[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.83:test +[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.83:test +[INFO] | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-web:jar:5.3.31:test +[INFO] | | +- org.springframework:spring-beans:jar:5.3.31:test +[INFO] | | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- org.springframework:spring-webmvc:jar:5.3.31:test +[INFO] | +- org.springframework:spring-aop:jar:5.3.31:test +[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-context:jar:5.3.31:test +[INFO] | | +- (org.springframework:spring-aop:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-expression:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-expression:jar:5.3.31:test +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.18:test +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.18:test +[INFO] | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.18:test +[INFO] | | +- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-test:jar:2.7.18:test - omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test +[INFO] | | +- net.minidev:json-smart:jar:2.4.7:test +[INFO] | | | \- net.minidev:accessors-smart:jar:2.4.7:test +[INFO] | | | \- org.ow2.asm:asm:jar:9.1:test +[INFO] | | \- (org.slf4j:slf4j-api:jar:1.7.33:test - omitted for conflict with 2.0.13) +[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test +[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test +[INFO] | +- (org.assertj:assertj-core:jar:3.22.0:test - omitted for conflict with 3.26.0) +[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test +[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test +[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | | +- (org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | | \- (org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | +- (org.mockito:mockito-core:jar:4.5.1:test - omitted for conflict with 5.12.0) +[INFO] | +- (org.mockito:mockito-junit-jupiter:jar:4.5.1:test - omitted for conflict with 5.12.0) +[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.1:test +[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test +[INFO] | +- org.springframework:spring-core:jar:5.3.31:test +[INFO] | | \- org.springframework:spring-jcl:jar:5.3.31:test +[INFO] | +- org.springframework:spring-test:jar:5.3.31:test +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.1:test +[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test - omitted for duplicate) +[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test (scope not updated to test) +[INFO] | +- org.junit.platform:junit-platform-engine:jar:1.10.2:test +[INFO] | | +- org.opentest4j:opentest4j:jar:1.3.0:test +[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test +[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test +[INFO] | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate) +[INFO] | | +- (org.junit.platform:junit-platform-commons:jar:1.10.2:test - omitted for duplicate) +[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | \- org.apiguardian:apiguardian-api:jar:1.1.2:test +[INFO] +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test (scope not updated to test) +[INFO] | +- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) +[INFO] | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] +- org.assertj:assertj-core:jar:3.26.0:test (scope not updated to test) +[INFO] | \- net.bytebuddy:byte-buddy:jar:1.14.16:test +[INFO] +- org.mockito:mockito-core:jar:5.12.0:test (scope not updated to test) +[INFO] | +- (net.bytebuddy:byte-buddy:jar:1.14.15:test - omitted for conflict with 1.14.16) +[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.14.15:test +[INFO] | \- org.objenesis:objenesis:jar:3.3:test +[INFO] +- org.mockito:mockito-junit-jupiter:jar:5.12.0:test (scope not updated to test) +[INFO] | +- (org.mockito:mockito-core:jar:5.12.0:test - omitted for duplicate) +[INFO] | \- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) +[INFO] +- net.jcip:jcip-annotations:jar:1.0:provided +[INFO] \- com.github.spotbugs:spotbugs-annotations:jar:4.8.6:provided +[INFO] \- com.google.code.findbugs:jsr305:jar:3.0.2:provided +[INFO] +[INFO] -------------< io.github.openfeign.form:feign-form-spring >------------- +[INFO] Building Open Feign Forms Extension for Spring 4.0.0 [3/3] +[INFO] from feign-form-spring/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- dependency:3.7.0:tree (default-cli) @ feign-form-spring --- +[INFO] io.github.openfeign.form:feign-form-spring:jar:4.0.0 +[INFO] +- io.github.openfeign.form:feign-form:jar:4.0.0:compile +[INFO] | \- (org.slf4j:slf4j-api:jar:2.0.13:compile - omitted for duplicate) +[INFO] +- org.springframework:spring-web:jar:5.3.31:compile +[INFO] | +- org.springframework:spring-beans:jar:5.3.31:compile (scope not updated to compile) +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:compile - omitted for duplicate) +[INFO] | \- org.springframework:spring-core:jar:5.3.31:compile (scope not updated to compile) +[INFO] | \- org.springframework:spring-jcl:jar:5.3.31:compile +[INFO] +- commons-fileupload:commons-fileupload:jar:1.5:compile +[INFO] | \- commons-io:commons-io:jar:2.11.0:compile +[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:test +[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.18:test +[INFO] | | +- org.springframework.boot:spring-boot:jar:2.7.18:test +[INFO] | | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-context:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test +[INFO] | | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.18:test +[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.12:test +[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.2.12:test +[INFO] | | | | \- (org.slf4j:slf4j-api:jar:1.7.32:test - omitted for conflict with 2.0.13) +[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:test +[INFO] | | | | +- (org.slf4j:slf4j-api:jar:1.7.35:test - omitted for conflict with 2.0.13) +[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:test +[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:test +[INFO] | | | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) +[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test +[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- org.yaml:snakeyaml:jar:1.30:test +[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.18:test +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.5:test +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.5:test +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.5:test +[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) +[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.18:test +[INFO] | | +- (jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test - omitted for duplicate) +[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test +[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.83:test +[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.83:test +[INFO] | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- org.springframework:spring-webmvc:jar:5.3.31:test +[INFO] | +- org.springframework:spring-aop:jar:5.3.31:test +[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-context:jar:5.3.31:test +[INFO] | | +- (org.springframework:spring-aop:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | | \- (org.springframework:spring-expression:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-expression:jar:5.3.31:test +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) +[INFO] +- org.springframework.cloud:spring-cloud-starter-openfeign:jar:3.1.9:test +[INFO] | +- org.springframework.cloud:spring-cloud-starter:jar:3.1.8:test +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.6.15:test - omitted for conflict with 2.7.18) +[INFO] | | +- org.springframework.cloud:spring-cloud-context:jar:3.1.8:test +[INFO] | | | \- (org.springframework.security:spring-security-crypto:jar:5.6.10:test - omitted for duplicate) +[INFO] | | +- (org.springframework.cloud:spring-cloud-commons:jar:3.1.8:test - omitted for duplicate) +[INFO] | | \- org.springframework.security:spring-security-rsa:jar:1.0.12.RELEASE:test +[INFO] | | \- org.bouncycastle:bcpkix-jdk18on:jar:1.73:test +[INFO] | | +- org.bouncycastle:bcprov-jdk18on:jar:1.73:test +[INFO] | | \- org.bouncycastle:bcutil-jdk18on:jar:1.73:test +[INFO] | | \- (org.bouncycastle:bcprov-jdk18on:jar:1.73:test - omitted for duplicate) +[INFO] | +- org.springframework.cloud:spring-cloud-openfeign-core:jar:3.1.9:test +[INFO] | | +- (org.springframework.boot:spring-boot-autoconfigure:jar:2.6.15:test - omitted for conflict with 2.7.18) +[INFO] | | +- org.springframework.boot:spring-boot-starter-aop:jar:2.6.15:test +[INFO] | | | +- (org.springframework.boot:spring-boot-starter:jar:2.6.15:test - omitted for conflict with 2.7.18) +[INFO] | | | +- (org.springframework:spring-aop:jar:5.3.27:test - omitted for conflict with 5.3.31) +[INFO] | | | \- org.aspectj:aspectjweaver:jar:1.9.7:test +[INFO] | | \- (commons-fileupload:commons-fileupload:jar:1.5:test - omitted for duplicate) +[INFO] | +- (org.springframework:spring-web:jar:5.3.27:test - omitted for conflict with 5.3.31) +[INFO] | +- org.springframework.cloud:spring-cloud-commons:jar:3.1.8:test +[INFO] | | \- org.springframework.security:spring-security-crypto:jar:5.6.10:test +[INFO] | +- (io.github.openfeign:feign-core:jar:11.10:test - omitted for conflict with 13.3) +[INFO] | \- io.github.openfeign:feign-slf4j:jar:11.10:test +[INFO] | +- (io.github.openfeign:feign-core:jar:11.10:test - omitted for conflict with 13.3) +[INFO] | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) +[INFO] +- org.projectlombok:lombok:jar:1.18.32:provided +[INFO] +- org.slf4j:slf4j-api:jar:2.0.13:compile +[INFO] +- io.github.openfeign:feign-core:jar:13.3:provided +[INFO] +- io.github.openfeign:feign-jackson:jar:13.3:test +[INFO] | +- (io.github.openfeign:feign-core:jar:13.3:test - omitted for duplicate) +[INFO] | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:test +[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:test +[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:test +[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.18:test +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.18:test +[INFO] | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.18:test +[INFO] | | +- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-test:jar:2.7.18:test - omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test - omitted for duplicate) +[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test +[INFO] | | +- net.minidev:json-smart:jar:2.4.7:test +[INFO] | | | \- net.minidev:accessors-smart:jar:2.4.7:test +[INFO] | | | \- org.ow2.asm:asm:jar:9.1:test +[INFO] | | \- (org.slf4j:slf4j-api:jar:1.7.33:test - omitted for conflict with 2.0.13) +[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test +[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test +[INFO] | +- (org.assertj:assertj-core:jar:3.22.0:test - omitted for conflict with 3.26.0) +[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test +[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test +[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | | +- (org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | | \- (org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test - omitted for conflict with 5.10.2) +[INFO] | +- (org.mockito:mockito-core:jar:4.5.1:test - omitted for conflict with 5.12.0) +[INFO] | +- (org.mockito:mockito-junit-jupiter:jar:4.5.1:test - omitted for conflict with 5.12.0) +[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.1:test +[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test +[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | +- org.springframework:spring-test:jar:5.3.31:test +[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) +[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.1:test +[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test - omitted for duplicate) +[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test (scope not updated to test) +[INFO] | +- org.junit.platform:junit-platform-engine:jar:1.10.2:test +[INFO] | | +- org.opentest4j:opentest4j:jar:1.3.0:test +[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test +[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test +[INFO] | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate) +[INFO] | | +- (org.junit.platform:junit-platform-commons:jar:1.10.2:test - omitted for duplicate) +[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | \- org.apiguardian:apiguardian-api:jar:1.1.2:test +[INFO] +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test (scope not updated to test) +[INFO] | +- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) +[INFO] | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] +- org.assertj:assertj-core:jar:3.26.0:test (scope not updated to test) +[INFO] | \- net.bytebuddy:byte-buddy:jar:1.14.16:test +[INFO] +- org.mockito:mockito-core:jar:5.12.0:test (scope not updated to test) +[INFO] | +- (net.bytebuddy:byte-buddy:jar:1.14.15:test - omitted for conflict with 1.14.16) +[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.14.15:test +[INFO] | \- org.objenesis:objenesis:jar:3.3:test +[INFO] +- org.mockito:mockito-junit-jupiter:jar:5.12.0:test (scope not updated to test) +[INFO] | +- (org.mockito:mockito-core:jar:5.12.0:test - omitted for duplicate) +[INFO] | \- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) +[INFO] +- net.jcip:jcip-annotations:jar:1.0:provided +[INFO] \- com.github.spotbugs:spotbugs-annotations:jar:4.8.6:provided +[INFO] \- com.google.code.findbugs:jsr305:jar:3.0.2:provided +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Summary for Open Feign Forms Parent 4.0.0: +[INFO] +[INFO] Open Feign Forms Parent ............................ SUCCESS [ 0.270 s] +[INFO] Open Feign Forms Core .............................. SUCCESS [ 0.032 s] +[INFO] Open Feign Forms Extension for Spring .............. SUCCESS [ 0.033 s] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 0.700 s +[INFO] Finished at: 2024-06-25T01:43:33+02:00 +[INFO] ------------------------------------------------------------------------ diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index 163112218..1d29440c5 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -44,27 +44,27 @@ limitations under the License. org.springframework spring-web - 5.1.5.RELEASE + 5.3.31 compile commons-fileupload commons-fileupload - 1.4 + 1.5 compile org.springframework.boot spring-boot-starter-web - 2.1.3.RELEASE + 2.7.18 test org.springframework.cloud spring-cloud-starter-openfeign - 2.1.1.RELEASE + 3.1.9 test diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java index 22d0ca2ac..202fe42a2 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java @@ -22,20 +22,20 @@ import java.lang.reflect.Type; import java.util.HashMap; +import lombok.val; +import org.springframework.web.multipart.MultipartFile; + import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; import feign.form.FormEncoder; import feign.form.MultipartFormContentProcessor; -import lombok.val; -import org.springframework.web.multipart.MultipartFile; - /** * Adds support for {@link MultipartFile} type to {@link FormEncoder}. * - * @author Tomasz Juchniewicz <tjuchniewicz@gmail.com> * @since 14.09.2016 + * @author Tomasz Juchniewicz <tjuchniewicz@gmail.com> */ public class SpringFormEncoder extends FormEncoder { diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java index f4796ec52..3d27b68a5 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java @@ -18,15 +18,16 @@ import static lombok.AccessLevel.PRIVATE; -import feign.codec.EncodeException; -import feign.form.multipart.AbstractWriter; -import feign.form.multipart.Output; - import lombok.experimental.FieldDefaults; import lombok.val; import org.springframework.web.multipart.MultipartFile; +import feign.codec.EncodeException; +import feign.form.multipart.AbstractWriter; +import feign.form.multipart.Output; + /** + * A Spring multiple files writer. * * @author Artem Labazin */ diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java index cb0377aa9..ddc687de3 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java @@ -18,13 +18,15 @@ import java.io.IOException; +import lombok.val; +import org.springframework.web.multipart.MultipartFile; + import feign.codec.EncodeException; import feign.form.multipart.AbstractWriter; import feign.form.multipart.Output; -import lombok.val; -import org.springframework.web.multipart.MultipartFile; /** + * A Spring single file writer. * * @author Artem Labazin */ diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java index e8f70d3a2..168033e49 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java @@ -21,10 +21,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import lombok.NonNull; import lombok.Value; +import lombok.val; import org.springframework.web.multipart.MultipartFile; /** @@ -60,14 +60,8 @@ public InputStream getInputStream () { @Override public void transferTo (File destination) throws IOException { - OutputStream outputStream = null; - try { - outputStream = new FileOutputStream(destination); + try (val outputStream = new FileOutputStream(destination)) { outputStream.write(bytes); - } finally { - if (outputStream != null) { - outputStream.close(); - } } } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java index 632baf862..8aac9cc5d 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java @@ -20,8 +20,8 @@ import java.util.Locale; /** - * A Map implementation that normalizes the key to UPPER CASE, so - * that value retrieval via the key is case insensitive. + * A Map implementation that normalizes the key to UPPER CASE, so that value + * retrieval via the key is case insensitive. */ final class IgnoreKeyCaseMap extends HashMap { diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index 6416a2339..47762d71e 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -16,7 +16,7 @@ package feign.form.spring.converter; -import static feign.form.util.CharsetUtil.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static lombok.AccessLevel.PRIVATE; import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index 7ad9165de..3152ad14a 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -23,10 +23,6 @@ import java.util.List; import java.util.Map; -import feign.Logger; -import feign.Response; -import feign.codec.Encoder; -import feign.form.spring.SpringFormEncoder; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; @@ -40,73 +36,82 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; -/** - * - * @author Artem Labazin - */ +import feign.Logger; +import feign.Response; +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; + @FeignClient( - name = "multipart-support-service", - url = "http://localhost:8080", - configuration = Client.ClientConfiguration.class + name = "multipart-support-service", + url = "http://localhost:8080", + configuration = Client.ClientConfiguration.class ) -public interface Client { +interface Client { @RequestMapping( - value = "/multipart/upload1/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + value = "/multipart/upload1/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) - String upload1 (@PathVariable("folder") String folder, - @RequestPart MultipartFile file, - @RequestParam(value = "message", required = false) String message); + String upload1 ( + @PathVariable("folder") String folder, + @RequestPart("file") MultipartFile file, + @RequestParam(name = "message", required = false) String message + ); @RequestMapping( - value = "/multipart/upload2/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + value = "/multipart/upload2/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) - String upload2 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message); + String upload2 ( + @RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(name = "message", required = false) String message + ); @RequestMapping( - value = "/multipart/upload3/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + value = "/multipart/upload3/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) - String upload3 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message); + String upload3 ( + @RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(name = "message", required = false) String message + ); @RequestMapping( - path = "/multipart/upload4/{id}", - method = POST, - produces = APPLICATION_JSON_VALUE + path = "/multipart/upload4/{id}", + method = POST, + produces = APPLICATION_JSON_VALUE ) - String upload4 (@PathVariable("id") String id, - @RequestBody Map map, - @RequestParam("userName") String userName); + String upload4 ( + @PathVariable("id") String id, + @RequestBody Map map, + @RequestParam("userName") String userName + ); @RequestMapping( - path = "/multipart/upload5", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload5", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) Response upload5 (Dto dto); @RequestMapping( - path = "/multipart/upload6", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload6", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) - String upload6Array (@RequestPart MultipartFile[] files); + String upload6Array (MultipartFile[] files); @RequestMapping( - path = "/multipart/upload6", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload6", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) - String upload6Collection (@RequestPart List files); + String upload6Collection (List files); class ClientConfiguration { @@ -114,12 +119,12 @@ class ClientConfiguration { private ObjectFactory messageConverters; @Bean - public Encoder feignEncoder () { + Encoder feignEncoder () { return new SpringFormEncoder(new SpringEncoder(messageConverters)); } @Bean - public Logger.Level feignLogger () { + Logger.Level feignLogger () { return Logger.Level.FULL; } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java index dfd925d24..6f74a1be2 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java @@ -18,9 +18,6 @@ import java.util.ArrayList; -import feign.Logger; -import feign.codec.Decoder; -import feign.form.spring.converter.SpringManyMultipartFilesReader; import lombok.val; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -33,12 +30,16 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; +import feign.Logger; +import feign.codec.Decoder; +import feign.form.spring.converter.SpringManyMultipartFilesReader; + @FeignClient( - name = "multipart-download-support-service", - url = "http://localhost:8081", - configuration = DownloadClient.ClientConfiguration.class + name = "multipart-download-support-service", + url = "http://localhost:8081", + configuration = DownloadClient.ClientConfiguration.class ) -public interface DownloadClient { +interface DownloadClient { @RequestMapping("/multipart/download/{fileId}") MultipartFile[] download (@PathVariable("fileId") String fileId); @@ -49,7 +50,7 @@ class ClientConfiguration { private ObjectFactory messageConverters; @Bean - public Decoder feignDecoder () { + Decoder feignDecoder () { val springConverters = messageConverters.getObject().getConverters(); val decoderConverters = new ArrayList>(springConverters.size() + 1); @@ -62,13 +63,13 @@ public Decoder feignDecoder () { @Override public HttpMessageConverters getObject () { - return httpMessageConverters; + return httpMessageConverters; } }); } @Bean - public Logger.Level feignLoggerLevel () { + Logger.Level feignLoggerLevel () { return Logger.Level.FULL; } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 42783b428..b4cce8652 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -47,11 +47,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; - -/** - * @author Tomasz Juchniewicz - * @since 22.08.2016 - */ @RestController @EnableFeignClients @SpringBootApplication @@ -59,22 +54,22 @@ public class Server { @RequestMapping( - path = "/multipart/upload1/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload1/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) @SneakyThrows public String upload1 (@PathVariable("folder") String folder, - @RequestPart MultipartFile file, + @RequestPart("file") MultipartFile file, @RequestParam(value = "message", required = false) String message ) { return new String(file.getBytes()) + ':' + message + ':' + folder; } @RequestMapping( - path = "/multipart/upload2/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload2/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) @SneakyThrows public String upload2 (@RequestBody MultipartFile file, @@ -85,9 +80,9 @@ public String upload2 (@RequestBody MultipartFile file, } @RequestMapping( - path = "/multipart/upload3/{folder}", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload3/{folder}", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) public String upload3 (@RequestBody MultipartFile file, @PathVariable("folder") String folder, @@ -105,9 +100,9 @@ public String upload4 (@PathVariable("id") String id, } @RequestMapping( - path = "/multipart/upload5", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload5", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) void upload5 (Dto dto) throws IOException { assert "field 1 value".equals(dto.getField1()); @@ -117,9 +112,9 @@ void upload5 (Dto dto) throws IOException { } @RequestMapping( - path = "/multipart/upload6", - method = POST, - consumes = MULTIPART_FORM_DATA_VALUE + path = "/multipart/upload6", + method = POST, + consumes = MULTIPART_FORM_DATA_VALUE ) public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa1, @RequestParam("popa2") MultipartFile popa2 @@ -134,9 +129,9 @@ public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa } @RequestMapping( - path = "/multipart/download/{fileId}", - method = GET, - produces = MULTIPART_FORM_DATA_VALUE + path = "/multipart/download/{fileId}", + method = GET, + produces = MULTIPART_FORM_DATA_VALUE ) public MultiValueMap download (@PathVariable("fileId") String fileId) { val multiParts = new LinkedMultiValueMap(); diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index 7ad9569ec..8889b1343 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -16,67 +16,59 @@ package feign.form.feign.spring; -import static feign.form.util.CharsetUtil.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import java.util.HashMap; import java.util.List; import lombok.val; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.multipart.MultipartFile; -/** - * @author Tomasz Juchniewicz - * @since 22.08.2016 - */ -@RunWith(SpringRunner.class) +import feign.Response; + @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class, - properties = { - "server.port=8080", - "feign.hystrix.enabled=false", - "logging.level.feign.form.feign.spring.Client=DEBUG" - } + webEnvironment = DEFINED_PORT, + classes = Server.class, + properties = { + "server.port=8080", + "feign.hystrix.enabled=false", + "logging.level.feign.form.feign.spring.Client=DEBUG" + } ) -public class SpringFormEncoderTest { +class SpringFormEncoderTest { @Autowired private Client client; @Test - public void upload1Test () throws Exception { + void upload1Test () throws Exception { val folder = "test_folder"; val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); val message = "message test"; - val response = client.upload1(folder, file, message); - - Assert.assertEquals(new String(file.getBytes()) + ':' + message + ':' + folder, response); + assertThat(client.upload1(folder, file, message)) + .isEqualTo(new String(file.getBytes()) + ':' + message + ':' + folder); } @Test - public void upload2Test () throws Exception { + void upload2Test () throws Exception { val folder = "test_folder"; val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); val message = "message test"; - String response = client.upload2(file, folder, message); - - Assert.assertEquals(new String(file.getBytes()) + ':' + message + ':' + folder, response); + assertThat(client.upload2(file, folder, message)) + .isEqualTo(new String(file.getBytes()) + ':' + message + ':' + folder); } @Test - public void uploadFileNameAndContentTypeTest () throws Exception { + void uploadFileNameAndContentTypeTest () throws Exception { val folder = "test_folder"; val file = new MockMultipartFile( "file", @@ -86,13 +78,12 @@ public void uploadFileNameAndContentTypeTest () throws Exception { ); val message = "message test"; - val response = client.upload3(file, folder, message); - - Assert.assertEquals(file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder, response); + assertThat(client.upload3(file, folder, message)) + .isEqualTo(file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder); } @Test - public void upload4Test () throws Exception { + void upload4Test () throws Exception { val map = new HashMap(); map.put("one", 1); map.put("two", 2); @@ -100,37 +91,38 @@ public void upload4Test () throws Exception { val userName = "popa"; val id = "42"; - val response = client.upload4(id, map, userName); - - Assert.assertEquals(userName + ':' + id + ':' + map.size(), response); + assertThat(client.upload4(id, map, userName)) + .isEqualTo(userName + ':' + id + ':' + map.size()); } @Test - public void upload5Test () throws Exception { + void upload5Test () throws Exception { val file = new MockMultipartFile("popa.txt", "Hello world".getBytes(UTF_8)); val dto = new Dto("field 1 value", 42, file); - val response = client.upload5(dto); - assertEquals(200, response.status()); + assertThat(client.upload5(dto)) + .isNotNull() + .extracting(Response::status) + .isEqualTo(200); } @Test - public void upload6ArrayTest () throws Exception { + void upload6ArrayTest () throws Exception { val file1 = new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)); val file2 = new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8)); - val response = client.upload6Array(new MultipartFile[] { file1, file2 }); - Assert.assertEquals("Hello world", response); + assertThat(client.upload6Array(new MultipartFile[] { file1, file2 })) + .isEqualTo("Hello world"); } @Test - public void upload6CollectionTest () throws Exception { + void upload6CollectionTest () throws Exception { List list = asList( (MultipartFile) new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)), (MultipartFile) new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8)) ); - val response = client.upload6Collection(list); - Assert.assertEquals("Hello world", response); + assertThat(client.upload6Collection(list)) + .isEqualTo("Hello world"); } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java index a2980f8d7..b4b420f9b 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java @@ -16,47 +16,52 @@ package feign.form.feign.spring; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import org.apache.commons.io.IOUtils; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.multipart.MultipartFile; -@RunWith(SpringRunner.class) @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class, - properties = { - "server.port=8081", - "feign.hystrix.enabled=false" - } + webEnvironment = DEFINED_PORT, + classes = Server.class, + properties = { + "server.port=8081", + "feign.hystrix.enabled=false" + } ) -public class SpringMultipartDecoderTest { +class SpringMultipartDecoderTest { @Autowired private DownloadClient downloadClient; @Test - public void downloadTest () throws Exception { + void downloadTest () throws Exception { MultipartFile[] downloads = downloadClient.download("123"); - Assert.assertEquals(2, downloads.length); + assertThat(downloads.length) + .isEqualTo(2); + + assertThat(downloads[0].getName()) + .isEqualTo("info"); - Assert.assertEquals("info", downloads[0].getName()); MediaType infoContentType = MediaType.parseMediaType(downloads[0].getContentType()); - Assert.assertTrue(MediaType.TEXT_PLAIN.includes(infoContentType)); - Assert.assertNotNull(infoContentType.getCharset()); - Assert.assertEquals("The text for file ID 123. Testing unicode €", - IOUtils.toString(downloads[0].getInputStream(), infoContentType.getCharset().name())); - - Assert.assertEquals("testfile.txt", downloads[1].getOriginalFilename()); - Assert.assertEquals(MediaType.APPLICATION_OCTET_STREAM_VALUE, downloads[1].getContentType()); - Assert.assertEquals(14, downloads[1].getSize()); + assertThat(MediaType.TEXT_PLAIN.includes(infoContentType)) + .isTrue(); + assertThat(infoContentType.getCharset()) + .isNotNull(); + assertThat(IOUtils.toString(downloads[0].getInputStream(), infoContentType.getCharset().name())) + .isEqualTo("The text for file ID 123. Testing unicode €"); + + assertThat(downloads[1].getOriginalFilename()) + .isEqualTo("testfile.txt"); + assertThat(downloads[1].getContentType()) + .isEqualTo(MediaType.APPLICATION_OCTET_STREAM_VALUE); + assertThat(downloads[1].getSize()) + .isEqualTo(14); } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java index 9eae57361..7672f5c85 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java @@ -17,6 +17,7 @@ package feign.form.feign.spring.converter; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; @@ -24,37 +25,44 @@ import java.io.IOException; import java.io.InputStream; -import feign.form.spring.converter.SpringManyMultipartFilesReader; import lombok.val; import org.apache.commons.io.IOUtils; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; -public class SpringManyMultipartFilesReaderTest { +import feign.form.spring.converter.SpringManyMultipartFilesReader; + +class SpringManyMultipartFilesReaderTest { private static final String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; @Test - public void readMultipartFormDataTest () throws IOException { + void readMultipartFormDataTest () throws IOException { val multipartFilesReader = new SpringManyMultipartFilesReader(4096); val multipartFiles = multipartFilesReader.read(MultipartFile[].class, new ValidMultipartMessage()); - Assert.assertEquals(2, multipartFiles.length); + assertThat(multipartFiles.length) + .isEqualTo(2); - Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, multipartFiles[0].getContentType()); - Assert.assertEquals("form-item-1", multipartFiles[0].getName()); - Assert.assertFalse(multipartFiles[0].isEmpty()); + assertThat(multipartFiles[0].getContentType()) + .isEqualTo(MediaType.APPLICATION_JSON_VALUE); + assertThat(multipartFiles[0].getName()) + .isEqualTo("form-item-1"); + assertThat(multipartFiles[0].isEmpty()) + .isFalse(); - Assert.assertEquals(MediaType.TEXT_PLAIN_VALUE, multipartFiles[1].getContentType()); - Assert.assertEquals("form-item-2-file-1", multipartFiles[1].getOriginalFilename()); - Assert.assertEquals("Plain text", IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")); + assertThat(multipartFiles[1].getContentType()) + .isEqualTo(MediaType.TEXT_PLAIN_VALUE); + assertThat(multipartFiles[1].getOriginalFilename()) + .isEqualTo("form-item-2-file-1"); + assertThat(IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")) + .isEqualTo("Plain text"); } - public static class ValidMultipartMessage implements HttpInputMessage { + static class ValidMultipartMessage implements HttpInputMessage { @Override public InputStream getBody () throws IOException { @@ -77,8 +85,8 @@ public InputStream getBody () throws IOException { public HttpHeaders getHeaders () { val httpHeaders = new HttpHeaders(); httpHeaders.put( - CONTENT_TYPE, - singletonList(MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY) + CONTENT_TYPE, + singletonList(MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY) ); return httpHeaders; } diff --git a/feign-form/src/main/java/feign/form/ContentProcessor.java b/feign-form/src/main/java/feign/form/ContentProcessor.java index 1f3c3762f..07f2b032d 100644 --- a/feign-form/src/main/java/feign/form/ContentProcessor.java +++ b/feign-form/src/main/java/feign/form/ContentProcessor.java @@ -32,8 +32,14 @@ */ public interface ContentProcessor { + /** + * A content type header name. + */ String CONTENT_TYPE_HEADER = "Content-Type"; + /** + * End line symbols. + */ String CRLF = "\r\n"; /** diff --git a/feign-form/src/main/java/feign/form/ContentType.java b/feign-form/src/main/java/feign/form/ContentType.java index f963abf4e..9dab93f45 100644 --- a/feign-form/src/main/java/feign/form/ContentType.java +++ b/feign-form/src/main/java/feign/form/ContentType.java @@ -31,8 +31,17 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public enum ContentType { + /** + * Unknown content type. + */ UNDEFINED("undefined"), + /** + * Url encoded content type. + */ URLENCODED("application/x-www-form-urlencoded"), + /** + * Multipart form data content type. + */ MULTIPART("multipart/form-data"); String header; diff --git a/feign-form/src/main/java/feign/form/FormData.java b/feign-form/src/main/java/feign/form/FormData.java index aaac80480..809d4e969 100644 --- a/feign-form/src/main/java/feign/form/FormData.java +++ b/feign-form/src/main/java/feign/form/FormData.java @@ -29,8 +29,8 @@ * This object encapsulates a byte array and its associated content type. * Use if if you want to specify the content type of your provided byte array. * - * @author Guillaume Simard * @since 24.03.2018 + * @author Guillaume Simard */ @Data @Builder diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index 40f6c6dfa..c6bb1c266 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -16,9 +16,9 @@ package feign.form; -import static feign.form.util.CharsetUtil.UTF_8; import static feign.form.util.PojoUtil.isUserPojo; import static feign.form.util.PojoUtil.toMap; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static lombok.AccessLevel.PRIVATE; @@ -29,13 +29,15 @@ import java.util.Map; import java.util.regex.Pattern; +import lombok.experimental.FieldDefaults; +import lombok.val; + import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; -import lombok.experimental.FieldDefaults; -import lombok.val; /** + * A Feign's form encoder. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/FormProperty.java b/feign-form/src/main/java/feign/form/FormProperty.java index 4f932ab9b..f022dc5b4 100644 --- a/feign-form/src/main/java/feign/form/FormProperty.java +++ b/feign-form/src/main/java/feign/form/FormProperty.java @@ -24,6 +24,7 @@ import java.lang.annotation.Target; /** + * A form property annotation to specify a field's name. * * @author marembo */ @@ -36,5 +37,4 @@ * The name of the property. */ String value (); - } diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index 36528849e..ac3bdc593 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -27,6 +27,9 @@ import java.util.LinkedList; import java.util.Map; +import lombok.experimental.FieldDefaults; +import lombok.val; + import feign.Request; import feign.RequestTemplate; import feign.codec.EncodeException; @@ -42,10 +45,8 @@ import feign.form.multipart.SingleParameterWriter; import feign.form.multipart.Writer; -import lombok.experimental.FieldDefaults; -import lombok.val; - /** + * Multipart form content processor. * * @author Artem Labazin */ @@ -62,7 +63,7 @@ public class MultipartFormContentProcessor implements ContentProcessor { * @param delegate specific delegate encoder for cases, when this processor couldn't handle request parameter. */ public MultipartFormContentProcessor (Encoder delegate) { - writers = new LinkedList(); + writers = new LinkedList<>(); addWriter(new ByteArrayWriter()); addWriter(new FormDataWriter()); addWriter(new SingleFileWriter()); @@ -77,35 +78,31 @@ public MultipartFormContentProcessor (Encoder delegate) { @Override public void process (RequestTemplate template, Charset charset, Map data) throws EncodeException { val boundary = Long.toHexString(System.currentTimeMillis()); - val output = new Output(charset); - - for (val entry : data.entrySet()) { - if (entry == null || entry.getKey() == null || entry.getValue() == null) { - continue; + try (val output = new Output(charset)) { + for (val entry : data.entrySet()) { + if (entry == null || entry.getKey() == null || entry.getValue() == null) { + continue; + } + val writer = findApplicableWriter(entry.getValue()); + writer.write(output, boundary, entry.getKey(), entry.getValue()); } - val writer = findApplicableWriter(entry.getValue()); - writer.write(output, boundary, entry.getKey(), entry.getValue()); - } - - output.write("--").write(boundary).write("--").write(CRLF); - val contentTypeHeaderValue = new StringBuilder() - .append(getSupportedContentType().getHeader()) - .append("; charset=").append(charset.name()) - .append("; boundary=").append(boundary) - .toString(); + output.write("--").write(boundary).write("--").write(CRLF); - template.header(CONTENT_TYPE_HEADER, Collections.emptyList()); // reset header - template.header(CONTENT_TYPE_HEADER, contentTypeHeaderValue); + val contentTypeHeaderValue = new StringBuilder() + .append(getSupportedContentType().getHeader()) + .append("; charset=").append(charset.name()) + .append("; boundary=").append(boundary) + .toString(); - // Feign's clients try to determine binary/string content by charset presence - // so, I set it to null (in spite of availability charset) for backward compatibility. - val bytes = output.toByteArray(); - val body = Request.Body.encoded(bytes, null); - template.body(body); + template.header(CONTENT_TYPE_HEADER, Collections.emptyList()); // reset header + template.header(CONTENT_TYPE_HEADER, contentTypeHeaderValue); - try { - output.close(); + // Feign's clients try to determine binary/string content by charset presence + // so, I set it to null (in spite of availability charset) for backward compatibility. + val bytes = output.toByteArray(); + val body = Request.Body.encoded(bytes, null); + template.body(body); } catch (IOException ex) { throw new EncodeException("Output closing error", ex); } diff --git a/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java index f911f3923..b414dd1f3 100644 --- a/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java @@ -25,13 +25,15 @@ import java.util.Map; import java.util.Map.Entry; +import lombok.SneakyThrows; +import lombok.val; + import feign.Request; import feign.RequestTemplate; import feign.codec.EncodeException; -import lombok.SneakyThrows; -import lombok.val; /** + * An URL encoded form content processor. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index 25ed7d5b0..982adf389 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -20,11 +20,13 @@ import java.net.URLConnection; -import feign.codec.EncodeException; import lombok.SneakyThrows; import lombok.val; +import feign.codec.EncodeException; + /** + * A base writer class. * * @author Artem Labazin */ @@ -47,8 +49,8 @@ public void write (Output output, String boundary, String key, Object value) thr * @throws EncodeException in case of write errors */ @SuppressWarnings({ - "PMD.UncommentedEmptyMethodBody", - "PMD.EmptyMethodInAbstractClassShouldBeAbstract" + "PMD.UncommentedEmptyMethodBody", + "PMD.EmptyMethodInAbstractClassShouldBeAbstract" }) protected void write (Output output, String key, Object value) throws EncodeException { } diff --git a/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java index 054ea4bf5..738fe60d0 100644 --- a/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java @@ -19,6 +19,7 @@ import feign.codec.EncodeException; /** + * A byte array writer. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java index 386860dba..5ece90fea 100644 --- a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java @@ -18,15 +18,16 @@ import static lombok.AccessLevel.PRIVATE; -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.codec.Encoder; - import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.val; +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; + /** + * A delegate writer. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java index c591e2bb2..1f9515c7a 100644 --- a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java @@ -16,15 +16,16 @@ package feign.form.multipart; +import lombok.val; + import feign.codec.EncodeException; import feign.form.FormData; -import lombok.val; - /** + * A {@link FormData} writer. * - * @author Guillaume Simard * @since 24.03.2018 + * @author Guillaume Simard */ public class FormDataWriter extends AbstractWriter { diff --git a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java index d3adce930..a485de9f5 100644 --- a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java @@ -20,11 +20,13 @@ import java.io.File; -import feign.codec.EncodeException; import lombok.experimental.FieldDefaults; import lombok.val; +import feign.codec.EncodeException; + /** + * A writer for multiple files. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java b/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java index 4b2bbd4cb..5e393dcd9 100644 --- a/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java @@ -18,11 +18,13 @@ import static lombok.AccessLevel.PRIVATE; -import feign.codec.EncodeException; import lombok.experimental.FieldDefaults; import lombok.val; +import feign.codec.EncodeException; + /** + * A multiple parameters writer. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java index c90e1ea58..20ee551cd 100644 --- a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -20,12 +20,14 @@ import static feign.form.util.PojoUtil.toMap; import static lombok.AccessLevel.PRIVATE; -import feign.codec.EncodeException; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.val; +import feign.codec.EncodeException; + /** + * A custom user's POJO writer. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java index 75b0a3f4b..78f614b7f 100644 --- a/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java @@ -21,15 +21,15 @@ import java.io.IOException; import java.io.InputStream; -import feign.codec.EncodeException; -import lombok.extern.slf4j.Slf4j; import lombok.val; +import feign.codec.EncodeException; + /** + * A single-file writer. * * @author Artem Labazin */ -@Slf4j public class SingleFileWriter extends AbstractWriter { @Override @@ -42,9 +42,7 @@ protected void write (Output output, String key, Object value) throws EncodeExce val file = (File) value; writeFileMetadata(output, key, file.getName(), null); - InputStream input = null; - try { - input = new FileInputStream(file); + try (InputStream input = new FileInputStream(file)) { val buf = new byte[4096]; int length = input.read(buf); while (length > 0) { @@ -54,14 +52,6 @@ protected void write (Output output, String key, Object value) throws EncodeExce } catch (IOException ex) { val message = String.format("Writing file's '%s' content error", file.getName()); throw new EncodeException(message, ex); - } finally { - if (input != null) { - try { - input.close(); - } catch (IOException ex) { - log.error("Closing file '{}' error", file.getName(), ex); - } - } } } } diff --git a/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java b/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java index 503ed77ea..017e670a9 100644 --- a/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java @@ -18,10 +18,12 @@ import static feign.form.ContentProcessor.CRLF; -import feign.codec.EncodeException; import lombok.val; +import feign.codec.EncodeException; + /** + * A writer for a single parameter. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/multipart/Writer.java b/feign-form/src/main/java/feign/form/multipart/Writer.java index ae1ff4e57..f47ac441c 100644 --- a/feign-form/src/main/java/feign/form/multipart/Writer.java +++ b/feign-form/src/main/java/feign/form/multipart/Writer.java @@ -19,6 +19,7 @@ import feign.codec.EncodeException; /** + * A writer interface. * * @author Artem Labazin */ diff --git a/feign-form/src/main/java/feign/form/util/CharsetUtil.java b/feign-form/src/main/java/feign/form/util/CharsetUtil.java deleted file mode 100644 index 49845f6a3..000000000 --- a/feign-form/src/main/java/feign/form/util/CharsetUtil.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * 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 feign.form.util; - -import java.nio.charset.Charset; -import java.rmi.UnexpectedException; - -/** - * - * @author Artem Labazin - */ -public final class CharsetUtil { - - public static final Charset UTF_8 = Charset.forName("UTF-8"); - - private CharsetUtil () throws UnexpectedException { - throw new UnexpectedException("It is not allowed to instantiate this class"); - } -} diff --git a/feign-form/src/main/java/feign/form/util/PojoUtil.java b/feign-form/src/main/java/feign/form/util/PojoUtil.java index ce70b6f0d..4904a11dc 100644 --- a/feign-form/src/main/java/feign/form/util/PojoUtil.java +++ b/feign-form/src/main/java/feign/form/util/PojoUtil.java @@ -23,15 +23,12 @@ import java.lang.reflect.Field; import java.lang.reflect.Type; import java.rmi.UnexpectedException; -import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; - import javax.annotation.Nullable; -import feign.form.FormProperty; - +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; @@ -39,7 +36,10 @@ import lombok.experimental.FieldDefaults; import lombok.val; +import feign.form.FormProperty; + /** + * An utility class to work with POJOs. * * @author Artem Labazin */ @@ -57,17 +57,16 @@ public static boolean isUserPojo (@NonNull Type type) { } @SneakyThrows + @SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") public static Map toMap (@NonNull Object object) { val result = new HashMap(); val type = object.getClass(); - val setAccessibleAction = new SetAccessibleAction(); for (val field : type.getDeclaredFields()) { val modifiers = field.getModifiers(); if (isFinal(modifiers) || isStatic(modifiers)) { continue; } - setAccessibleAction.setField(field); - AccessController.doPrivileged(setAccessibleAction); + field.setAccessible(true); val fieldValue = field.get(object); if (fieldValue == null) { @@ -90,7 +89,7 @@ private PojoUtil () throws UnexpectedException { @Setter @NoArgsConstructor @FieldDefaults(level = PRIVATE) - private static class SetAccessibleAction implements PrivilegedAction { + private static final class SetAccessibleAction implements PrivilegedAction { @Nullable Field field; diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index 50dd3a9de..bcbb46424 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -28,25 +28,19 @@ import java.util.Arrays; import java.util.Map; -import feign.Feign; -import feign.jackson.JacksonEncoder; import lombok.val; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -/** - * @author Artem Labazin - * @since 30.04.2016 - */ -@RunWith(SpringRunner.class) +import feign.Feign; +import feign.Response; +import feign.jackson.JacksonEncoder; + @SpringBootTest( webEnvironment = DEFINED_PORT, classes = Server.class ) -public class BasicClientTest { +class BasicClientTest { private static final TestClient API; @@ -59,114 +53,130 @@ public class BasicClientTest { } @Test - public void testForm () { - val response = API.form("1", "1"); - - Assert.assertNotNull(response); - Assert.assertEquals(200, response.status()); + void testForm () { + assertThat(API.form("1", "1")) + .isNotNull() + .extracting(Response::status) + .isEqualTo(200); } @Test - public void testFormException () { - val response = API.form("1", "2"); - - Assert.assertNotNull(response); - Assert.assertEquals(400, response.status()); + void testFormException () { + assertThat(API.form("1", "2")) + .isNotNull() + .extracting(Response::status) + .isEqualTo(400); } @Test - public void testUpload () throws Exception { + void testUpload () throws Exception { val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - Assert.assertTrue(Files.exists(path)); + assertThat(path) + .exists(); - val stringResponse = API.upload(path.toFile()); - Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); + assertThat(API.upload(path.toFile())) + .asLong() + .isEqualTo(Files.size(path)); } @Test - public void testUploadWithParam () throws Exception { + void testUploadWithParam () throws Exception { val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - Assert.assertTrue(Files.exists(path)); + assertThat(path) + .exists(); - val stringResponse = API.upload(10, Boolean.TRUE, path.toFile()); - Assert.assertEquals(Files.size(path), Long.parseLong(stringResponse)); + assertThat(API.upload(10, Boolean.TRUE, path.toFile())) + .asLong() + .isEqualTo(Files.size(path)); } @Test - public void testJson () { + void testJson () { val dto = new Dto("Artem", 11); - val stringResponse = API.json(dto); - Assert.assertEquals("ok", stringResponse); + assertThat(API.json(dto)) + .isEqualTo("ok"); } @Test - public void testQueryMap () { + void testQueryMap () { Map value = singletonMap("filter", (Object) asList("one", "two", "three", "four")); - val stringResponse = API.queryMap(value); - Assert.assertEquals("4", stringResponse); + assertThat(API.queryMap(value)) + .isEqualTo("4"); } @Test - public void testMultipleFilesArray () throws Exception { + void testMultipleFilesArray () throws Exception { val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - Assert.assertTrue(Files.exists(path1)); + assertThat(path1) + .exists(); + val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); - Assert.assertTrue(Files.exists(path2)); + assertThat(path2) + .exists(); - val stringResponse = API.uploadWithArray(new File[] { path1.toFile(), path2.toFile() }); - Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); + assertThat(API.uploadWithArray(new File[] { path1.toFile(), path2.toFile() })) + .asLong() + .isEqualTo(Files.size(path1) + Files.size(path2)); } @Test - public void testMultipleFilesList () throws Exception { + void testMultipleFilesList () throws Exception { val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - Assert.assertTrue(Files.exists(path1)); + assertThat(path1) + .exists(); + val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); - Assert.assertTrue(Files.exists(path2)); + assertThat(path2) + .exists(); - val stringResponse = API.uploadWithList(asList(path1.toFile(), path2.toFile())); - Assert.assertEquals(Files.size(path1) + Files.size(path2), Long.parseLong(stringResponse)); + assertThat(API.uploadWithList(asList(path1.toFile(), path2.toFile()))) + .asLong() + .isEqualTo(Files.size(path1) + Files.size(path2)); } @Test - public void testUploadWithDto () throws Exception { + void testUploadWithDto () throws Exception { val dto = new Dto("Artem", 11); val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - Assert.assertTrue(Files.exists(path)); + assertThat(path) + .exists(); - val response = API.uploadWithDto(dto, path.toFile()); - Assert.assertNotNull(response); - Assert.assertEquals(200, response.status()); + assertThat(API.uploadWithDto(dto, path.toFile())) + .isNotNull() + .extracting(Response::status) + .isEqualTo(200); } @Test - public void testUnknownTypeFile () throws Exception { + void testUnknownTypeFile () throws Exception { val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.abc").toURI()); - Assert.assertTrue(Files.exists(path)); + assertThat(path) + .exists(); - val stringResponse = API.uploadUnknownType(path.toFile()); - Assert.assertEquals("application/octet-stream", stringResponse); + assertThat(API.uploadUnknownType(path.toFile())) + .isEqualTo("application/octet-stream"); } @Test - public void testFormData () throws Exception { + void testFormData () throws Exception { val formData = new FormData("application/custom-type", "popa.txt", "Allo".getBytes("UTF-8")); - val stringResponse = API.uploadFormData(formData); - Assert.assertEquals("popa.txt:application/custom-type", stringResponse); + + assertThat(API.uploadFormData(formData)) + .isEqualTo("popa.txt:application/custom-type"); } @Test - public void testSubmitRepeatableQueryParam () throws Exception { + void testSubmitRepeatableQueryParam () throws Exception { val names = new String[] { "Milada", "Thais" }; val stringResponse = API.submitRepeatableQueryParam(names); assertThat(stringResponse).isEqualTo("Milada and Thais"); } @Test - public void testSubmitRepeatableFormParam () throws Exception { + void testSubmitRepeatableFormParam () throws Exception { val names = Arrays.asList("Milada", "Thais"); val stringResponse = API.submitRepeatableFormParam(names); assertThat(stringResponse).isEqualTo("Milada and Thais"); diff --git a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java index e093009aa..222ca9e1b 100644 --- a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java +++ b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java @@ -17,28 +17,25 @@ package feign.form; import static feign.Logger.Level.FULL; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import lombok.val; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + import feign.Feign; import feign.Headers; import feign.Param; import feign.RequestLine; import feign.Response; import feign.jackson.JacksonEncoder; -import lombok.val; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = DEFINED_PORT, classes = Server.class ) -public class ByteArrayClientTest { +class ByteArrayClientTest { private static final CustomClient API; @@ -53,15 +50,16 @@ public class ByteArrayClientTest { } @Test - public void testNotTreatedAsFileUpload () { + void testNotTreatedAsFileUpload () { byte[] bytes = "Hello World".getBytes(); - val response = API.uploadByteArray(bytes); - assertNotNull(response); - assertEquals(200, response.status()); + assertThat(API.uploadByteArray(bytes)) + .isNotNull() + .extracting(Response::status) + .isEqualTo(200); } - public interface CustomClient { + interface CustomClient { @RequestLine("POST /upload/byte_array_parameter") @Headers("Content-Type: multipart/form-data") diff --git a/feign-form/src/test/java/feign/form/CustomClientTest.java b/feign-form/src/test/java/feign/form/CustomClientTest.java index 5a6d59852..373f731f1 100644 --- a/feign-form/src/test/java/feign/form/CustomClientTest.java +++ b/feign-form/src/test/java/feign/form/CustomClientTest.java @@ -18,10 +18,13 @@ import static feign.Logger.Level.FULL; import static feign.form.ContentType.MULTIPART; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import lombok.val; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + import feign.Feign; import feign.Headers; import feign.Param; @@ -30,22 +33,12 @@ import feign.form.multipart.ByteArrayWriter; import feign.form.multipart.Output; import feign.jackson.JacksonEncoder; -import lombok.val; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -/** - * @author Artem Labazin - * @since 27.11.2017 - */ -@RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = DEFINED_PORT, classes = Server.class ) -public class CustomClientTest { +class CustomClientTest { private static final CustomClient API; @@ -62,14 +55,13 @@ public class CustomClientTest { } @Test - public void test () { - val stringResponse = API.uploadByteArray(new byte[0]); - - assertNotNull(stringResponse); - assertEquals("popa.txt", stringResponse); + void test () { + assertThat(API.uploadByteArray(new byte[0])) + .isNotNull() + .isEqualTo("popa.txt"); } - private static class CustomByteArrayWriter extends ByteArrayWriter { + private static final class CustomByteArrayWriter extends ByteArrayWriter { @Override protected void write (Output output, String key, Object value) throws EncodeException { @@ -80,7 +72,7 @@ protected void write (Output output, String key, Object value) throws EncodeExce } } - public interface CustomClient { + interface CustomClient { @RequestLine("POST /upload/byte_array") @Headers("Content-Type: multipart/form-data") diff --git a/feign-form/src/test/java/feign/form/Dto.java b/feign-form/src/test/java/feign/form/Dto.java index 83504a1c1..6a5dfc497 100644 --- a/feign-form/src/test/java/feign/form/Dto.java +++ b/feign-form/src/test/java/feign/form/Dto.java @@ -25,15 +25,11 @@ import lombok.NoArgsConstructor; import lombok.experimental.FieldDefaults; -/** - * @author Artem Labazin - * @since 01.05.2016 - */ @Data @NoArgsConstructor @AllArgsConstructor @FieldDefaults(level = PRIVATE) -public class Dto implements Serializable { +class Dto implements Serializable { private static final long serialVersionUID = 4743133513526293872L; diff --git a/feign-form/src/test/java/feign/form/FormDto.java b/feign-form/src/test/java/feign/form/FormDto.java index c82a3494b..e0eb0a35c 100644 --- a/feign-form/src/test/java/feign/form/FormDto.java +++ b/feign-form/src/test/java/feign/form/FormDto.java @@ -23,10 +23,6 @@ import lombok.NoArgsConstructor; import lombok.experimental.FieldDefaults; -/** - * - * @author marembo - */ @Data @NoArgsConstructor @AllArgsConstructor diff --git a/feign-form/src/test/java/feign/form/FormPropertyTest.java b/feign-form/src/test/java/feign/form/FormPropertyTest.java index 68b190182..53ef11957 100644 --- a/feign-form/src/test/java/feign/form/FormPropertyTest.java +++ b/feign-form/src/test/java/feign/form/FormPropertyTest.java @@ -17,32 +17,23 @@ package feign.form; import static feign.Logger.Level.FULL; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import lombok.val; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + import feign.Feign; import feign.Headers; import feign.RequestLine; import feign.jackson.JacksonEncoder; -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -/** - * - * @author marembo - */ -@Slf4j -@RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = DEFINED_PORT, classes = Server.class ) -public class FormPropertyTest { +class FormPropertyTest { private static final FormClient API; @@ -55,18 +46,14 @@ public class FormPropertyTest { } @Test - public void test () { + void test () { val dto = new FormDto("Amigo", 23); - val stringResponse = API.postData(dto); - - assertNotNull(stringResponse); - - log.info("Response: {}", stringResponse); - assertEquals("Amigo=23", stringResponse); + assertThat(API.postData(dto)) + .isEqualTo("Amigo=23"); } - public interface FormClient { + interface FormClient { @RequestLine("POST /form-data") @Headers("Content-Type: application/x-www-form-urlencoded") diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 53ab82f88..80147fa70 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -46,10 +46,6 @@ import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; -/** - * @author Artem Labazin - * @since 30.04.2016 - */ @Controller @SpringBootApplication @SuppressWarnings("checkstyle:DesignForExtension") diff --git a/feign-form/src/test/java/feign/form/TestClient.java b/feign-form/src/test/java/feign/form/TestClient.java index f0d49723d..92b525161 100644 --- a/feign-form/src/test/java/feign/form/TestClient.java +++ b/feign-form/src/test/java/feign/form/TestClient.java @@ -27,10 +27,6 @@ import feign.RequestLine; import feign.Response; -/** - * - * @author Artem Labazin - */ public interface TestClient { @RequestLine("POST /form") diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index 07bc46683..38d99ed19 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -17,34 +17,32 @@ package feign.form; import static feign.Logger.Level.FULL; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import java.util.HashMap; import java.util.Map; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + import feign.Feign; import feign.Headers; import feign.Logger; import feign.RequestLine; import feign.Response; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = DEFINED_PORT, classes = Server.class ) -public class WildCardMapTest { +class WildCardMapTest { private static FormUrlEncodedApi api; - @BeforeClass - public static void configureClient () { + @BeforeAll + static void configureClient () { api = Feign.builder() .encoder(new FormEncoder()) .logger(new Logger.JavaLogger().appendToFile("log.txt")) @@ -53,7 +51,7 @@ public static void configureClient () { } @Test - public void testOk () { + void testOk () { Map param = new HashMap() { private static final long serialVersionUID = 3109256773218160485L; @@ -63,12 +61,15 @@ public void testOk () { put("key2", "1"); } }; - Response response = api.wildCardMap(param); - Assert.assertEquals(200, response.status()); + + assertThat(api.wildCardMap(param)) + .isNotNull() + .extracting(Response::status) + .isEqualTo(200); } @Test - public void testBadRequest () { + void testBadRequest () { Map param = new HashMap() { private static final long serialVersionUID = 3109256773218160485L; @@ -79,8 +80,11 @@ public void testBadRequest () { put("key2", "2"); } }; - Response response = api.wildCardMap(param); - Assert.assertEquals(418, response.status()); + + assertThat(api.wildCardMap(param)) + .isNotNull() + .extracting(Response::status) + .isEqualTo(418); } interface FormUrlEncodedApi { diff --git a/pom.xml b/pom.xml index f4718f291..a0cae8821 100644 --- a/pom.xml +++ b/pom.xml @@ -36,11 +36,17 @@ limitations under the License. UTF-8 UTF-8 - 1.6 - 1.6 + 1.8 - 1.8 - 1.8 + ${java.version} + ${java.version} + + ${java.version} + ${java.version} + + false + ${skipAllTests} + ${skipAllTests} Open Feign Forms Parent @@ -119,73 +125,73 @@ limitations under the License. org.projectlombok lombok - 1.18.6 + 1.18.32 provided org.slf4j slf4j-api - 1.7.26 + 2.0.13 io.github.openfeign feign-core - 10.2.0 + 13.3 provided io.github.openfeign feign-jackson - 10.2.0 + 13.3 test org.springframework.boot spring-boot-starter-web - 2.1.3.RELEASE + 2.7.18 test org.springframework.boot spring-boot-starter-test - 2.1.3.RELEASE + 2.7.18 test org.junit.jupiter junit-jupiter-engine - 5.4.1 + 5.10.2 test org.junit.jupiter junit-jupiter-params - 5.4.1 + 5.10.2 test org.assertj assertj-core - 3.12.2 + 3.26.0 test org.mockito mockito-core - 2.25.1 + 5.12.0 test org.mockito mockito-junit-jupiter - 2.25.1 + 5.12.0 test @@ -198,15 +204,15 @@ limitations under the License. com.github.spotbugs spotbugs-annotations - 3.1.12 + 4.8.6 provided - + @@ -214,7 +220,7 @@ limitations under the License. maven-jar-plugin - 3.0.2 + 3.4.2 ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -225,7 +231,7 @@ limitations under the License. com.github.spotbugs spotbugs-maven-plugin - 3.1.11 + 4.8.5.0 Max Low @@ -245,9 +251,8 @@ limitations under the License. org.apache.maven.plugins maven-pmd-plugin - 3.11.0 + 3.23.0 - ${project.build.sourceEncoding} ${maven.compiler.source} true true @@ -266,17 +271,29 @@ limitations under the License. + + + net.sourceforge.pmd + pmd-core + 7.2.0 + + + net.sourceforge.pmd + pmd-java + 7.2.0 + + org.apache.maven.plugins maven-checkstyle-plugin - 3.0.0 + 3.4.0 com.puppycrawl.tools checkstyle - 8.18 + 10.17.0 @@ -302,7 +319,7 @@ limitations under the License. org.apache.felix maven-bundle-plugin - 3.2.0 + 5.1.9 bundle-manifest @@ -320,7 +337,7 @@ limitations under the License. org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.3.1 attach-sources @@ -334,7 +351,7 @@ limitations under the License. pl.project13.maven git-commit-id-plugin - 2.2.6 + 4.9.10 git-infos @@ -368,16 +385,16 @@ limitations under the License. org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.7.0 -Xdoclint:none - -Xdoclint:none true - UTF-8 - UTF-8 - UTF-8 + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} true protected + ${java.version} true @@ -393,7 +410,10 @@ limitations under the License. maven-compiler-plugin - 3.8.0 + 3.13.0 + + ${main.java.version} + @@ -413,7 +433,7 @@ limitations under the License. org.codehaus.mojo animal-sniffer-maven-plugin - 1.16 + 1.23 signature-check @@ -426,7 +446,7 @@ limitations under the License. org.codehaus.mojo.signature - java16 + java18 1.0 @@ -434,7 +454,7 @@ limitations under the License. maven-install-plugin - 2.5.2 + 3.1.2 true @@ -444,7 +464,7 @@ limitations under the License. org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.7.0 true ossrh @@ -457,7 +477,7 @@ limitations under the License. org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.4 sign-artifacts From 1558f0b7d5ec0d0d2d6c4c83c8b1a2db4951f0c6 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 25 Jun 2024 02:15:59 +0200 Subject: [PATCH 86/93] remove garbage --- deps.tree | 442 ------------------------------------------------------ 1 file changed, 442 deletions(-) delete mode 100644 deps.tree diff --git a/deps.tree b/deps.tree deleted file mode 100644 index 4a5dfbd3a..000000000 --- a/deps.tree +++ /dev/null @@ -1,442 +0,0 @@ -[INFO] Scanning for projects... -[INFO] Inspecting build with total of 3 modules... -[INFO] Installing Nexus Staging features: -[INFO] ... total of 3 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin -[INFO] ------------------------------------------------------------------------ -[INFO] Reactor Build Order: -[INFO] -[INFO] Open Feign Forms Parent [pom] -[INFO] Open Feign Forms Core [jar] -[INFO] Open Feign Forms Extension for Spring [jar] -[INFO] -[INFO] ------------------< io.github.openfeign.form:parent >------------------- -[INFO] Building Open Feign Forms Parent 4.0.0 [1/3] -[INFO] from pom.xml -[INFO] --------------------------------[ pom ]--------------------------------- -[INFO] -[INFO] --- dependency:3.7.0:tree (default-cli) @ parent --- -[INFO] io.github.openfeign.form:parent:pom:4.0.0 -[INFO] +- org.projectlombok:lombok:jar:1.18.32:provided -[INFO] +- org.slf4j:slf4j-api:jar:2.0.13:compile -[INFO] +- io.github.openfeign:feign-core:jar:13.3:provided -[INFO] +- io.github.openfeign:feign-jackson:jar:13.3:test -[INFO] | +- (io.github.openfeign:feign-core:jar:13.3:test - omitted for duplicate) -[INFO] | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:test -[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:test -[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:test -[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:test -[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.18:test -[INFO] | | +- org.springframework.boot:spring-boot:jar:2.7.18:test -[INFO] | | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | | \- (org.springframework:spring-context:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test -[INFO] | | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.18:test -[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.12:test -[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.2.12:test -[INFO] | | | | \- (org.slf4j:slf4j-api:jar:1.7.32:test - omitted for conflict with 2.0.13) -[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:test -[INFO] | | | | +- (org.slf4j:slf4j-api:jar:1.7.35:test - omitted for conflict with 2.0.13) -[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:test -[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:test -[INFO] | | | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) -[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test -[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- org.yaml:snakeyaml:jar:1.30:test -[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.18:test -[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.5:test -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.5:test -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.5:test -[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.18:test -[INFO] | | +- (jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test - omitted for duplicate) -[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test -[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.83:test -[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.83:test -[INFO] | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-web:jar:5.3.31:test -[INFO] | | +- org.springframework:spring-beans:jar:5.3.31:test -[INFO] | | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- org.springframework:spring-webmvc:jar:5.3.31:test -[INFO] | +- org.springframework:spring-aop:jar:5.3.31:test -[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-context:jar:5.3.31:test -[INFO] | | +- (org.springframework:spring-aop:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-expression:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-expression:jar:5.3.31:test -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) -[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.18:test -[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.18:test -[INFO] | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.18:test -[INFO] | | +- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- (org.springframework.boot:spring-boot-test:jar:2.7.18:test - omitted for duplicate) -[INFO] | | \- (org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test -[INFO] | | +- net.minidev:json-smart:jar:2.4.7:test -[INFO] | | | \- net.minidev:accessors-smart:jar:2.4.7:test -[INFO] | | | \- org.ow2.asm:asm:jar:9.1:test -[INFO] | | \- (org.slf4j:slf4j-api:jar:1.7.33:test - omitted for conflict with 2.0.13) -[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test -[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test -[INFO] | +- (org.assertj:assertj-core:jar:3.22.0:test - omitted for conflict with 3.26.0) -[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test -[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test -[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | | +- (org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | | \- (org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | +- (org.mockito:mockito-core:jar:4.5.1:test - omitted for conflict with 5.12.0) -[INFO] | +- (org.mockito:mockito-junit-jupiter:jar:4.5.1:test - omitted for conflict with 5.12.0) -[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.1:test -[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test -[INFO] | +- org.springframework:spring-core:jar:5.3.31:test -[INFO] | | \- org.springframework:spring-jcl:jar:5.3.31:test -[INFO] | +- org.springframework:spring-test:jar:5.3.31:test -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.1:test -[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test - omitted for duplicate) -[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test (scope not updated to test) -[INFO] | +- org.junit.platform:junit-platform-engine:jar:1.10.2:test -[INFO] | | +- org.opentest4j:opentest4j:jar:1.3.0:test -[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test -[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test -[INFO] | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate) -[INFO] | | +- (org.junit.platform:junit-platform-commons:jar:1.10.2:test - omitted for duplicate) -[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | \- org.apiguardian:apiguardian-api:jar:1.1.2:test -[INFO] +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test (scope not updated to test) -[INFO] | +- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) -[INFO] | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] +- org.assertj:assertj-core:jar:3.26.0:test (scope not updated to test) -[INFO] | \- net.bytebuddy:byte-buddy:jar:1.14.16:test -[INFO] +- org.mockito:mockito-core:jar:5.12.0:test (scope not updated to test) -[INFO] | +- (net.bytebuddy:byte-buddy:jar:1.14.15:test - omitted for conflict with 1.14.16) -[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.14.15:test -[INFO] | \- org.objenesis:objenesis:jar:3.3:test -[INFO] +- org.mockito:mockito-junit-jupiter:jar:5.12.0:test (scope not updated to test) -[INFO] | +- (org.mockito:mockito-core:jar:5.12.0:test - omitted for duplicate) -[INFO] | \- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) -[INFO] +- net.jcip:jcip-annotations:jar:1.0:provided -[INFO] \- com.github.spotbugs:spotbugs-annotations:jar:4.8.6:provided -[INFO] \- com.google.code.findbugs:jsr305:jar:3.0.2:provided -[INFO] -[INFO] ----------------< io.github.openfeign.form:feign-form >----------------- -[INFO] Building Open Feign Forms Core 4.0.0 [2/3] -[INFO] from feign-form/pom.xml -[INFO] --------------------------------[ jar ]--------------------------------- -[INFO] -[INFO] --- dependency:3.7.0:tree (default-cli) @ feign-form --- -[INFO] io.github.openfeign.form:feign-form:jar:4.0.0 -[INFO] +- org.projectlombok:lombok:jar:1.18.32:provided -[INFO] +- org.slf4j:slf4j-api:jar:2.0.13:compile -[INFO] +- io.github.openfeign:feign-core:jar:13.3:provided -[INFO] +- io.github.openfeign:feign-jackson:jar:13.3:test -[INFO] | +- (io.github.openfeign:feign-core:jar:13.3:test - omitted for duplicate) -[INFO] | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:test -[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:test -[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:test -[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:test -[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.18:test -[INFO] | | +- org.springframework.boot:spring-boot:jar:2.7.18:test -[INFO] | | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | | \- (org.springframework:spring-context:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test -[INFO] | | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.18:test -[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.12:test -[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.2.12:test -[INFO] | | | | \- (org.slf4j:slf4j-api:jar:1.7.32:test - omitted for conflict with 2.0.13) -[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:test -[INFO] | | | | +- (org.slf4j:slf4j-api:jar:1.7.35:test - omitted for conflict with 2.0.13) -[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:test -[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:test -[INFO] | | | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) -[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test -[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- org.yaml:snakeyaml:jar:1.30:test -[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.18:test -[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.5:test -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.5:test -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.5:test -[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.18:test -[INFO] | | +- (jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test - omitted for duplicate) -[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test -[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.83:test -[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.83:test -[INFO] | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-web:jar:5.3.31:test -[INFO] | | +- org.springframework:spring-beans:jar:5.3.31:test -[INFO] | | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- org.springframework:spring-webmvc:jar:5.3.31:test -[INFO] | +- org.springframework:spring-aop:jar:5.3.31:test -[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-context:jar:5.3.31:test -[INFO] | | +- (org.springframework:spring-aop:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-expression:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-expression:jar:5.3.31:test -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) -[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.18:test -[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.18:test -[INFO] | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.18:test -[INFO] | | +- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- (org.springframework.boot:spring-boot-test:jar:2.7.18:test - omitted for duplicate) -[INFO] | | \- (org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test -[INFO] | | +- net.minidev:json-smart:jar:2.4.7:test -[INFO] | | | \- net.minidev:accessors-smart:jar:2.4.7:test -[INFO] | | | \- org.ow2.asm:asm:jar:9.1:test -[INFO] | | \- (org.slf4j:slf4j-api:jar:1.7.33:test - omitted for conflict with 2.0.13) -[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test -[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test -[INFO] | +- (org.assertj:assertj-core:jar:3.22.0:test - omitted for conflict with 3.26.0) -[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test -[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test -[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | | +- (org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | | \- (org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | +- (org.mockito:mockito-core:jar:4.5.1:test - omitted for conflict with 5.12.0) -[INFO] | +- (org.mockito:mockito-junit-jupiter:jar:4.5.1:test - omitted for conflict with 5.12.0) -[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.1:test -[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test -[INFO] | +- org.springframework:spring-core:jar:5.3.31:test -[INFO] | | \- org.springframework:spring-jcl:jar:5.3.31:test -[INFO] | +- org.springframework:spring-test:jar:5.3.31:test -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.1:test -[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test - omitted for duplicate) -[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test (scope not updated to test) -[INFO] | +- org.junit.platform:junit-platform-engine:jar:1.10.2:test -[INFO] | | +- org.opentest4j:opentest4j:jar:1.3.0:test -[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test -[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test -[INFO] | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate) -[INFO] | | +- (org.junit.platform:junit-platform-commons:jar:1.10.2:test - omitted for duplicate) -[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | \- org.apiguardian:apiguardian-api:jar:1.1.2:test -[INFO] +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test (scope not updated to test) -[INFO] | +- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) -[INFO] | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] +- org.assertj:assertj-core:jar:3.26.0:test (scope not updated to test) -[INFO] | \- net.bytebuddy:byte-buddy:jar:1.14.16:test -[INFO] +- org.mockito:mockito-core:jar:5.12.0:test (scope not updated to test) -[INFO] | +- (net.bytebuddy:byte-buddy:jar:1.14.15:test - omitted for conflict with 1.14.16) -[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.14.15:test -[INFO] | \- org.objenesis:objenesis:jar:3.3:test -[INFO] +- org.mockito:mockito-junit-jupiter:jar:5.12.0:test (scope not updated to test) -[INFO] | +- (org.mockito:mockito-core:jar:5.12.0:test - omitted for duplicate) -[INFO] | \- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) -[INFO] +- net.jcip:jcip-annotations:jar:1.0:provided -[INFO] \- com.github.spotbugs:spotbugs-annotations:jar:4.8.6:provided -[INFO] \- com.google.code.findbugs:jsr305:jar:3.0.2:provided -[INFO] -[INFO] -------------< io.github.openfeign.form:feign-form-spring >------------- -[INFO] Building Open Feign Forms Extension for Spring 4.0.0 [3/3] -[INFO] from feign-form-spring/pom.xml -[INFO] --------------------------------[ jar ]--------------------------------- -[INFO] -[INFO] --- dependency:3.7.0:tree (default-cli) @ feign-form-spring --- -[INFO] io.github.openfeign.form:feign-form-spring:jar:4.0.0 -[INFO] +- io.github.openfeign.form:feign-form:jar:4.0.0:compile -[INFO] | \- (org.slf4j:slf4j-api:jar:2.0.13:compile - omitted for duplicate) -[INFO] +- org.springframework:spring-web:jar:5.3.31:compile -[INFO] | +- org.springframework:spring-beans:jar:5.3.31:compile (scope not updated to compile) -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:compile - omitted for duplicate) -[INFO] | \- org.springframework:spring-core:jar:5.3.31:compile (scope not updated to compile) -[INFO] | \- org.springframework:spring-jcl:jar:5.3.31:compile -[INFO] +- commons-fileupload:commons-fileupload:jar:1.5:compile -[INFO] | \- commons-io:commons-io:jar:2.11.0:compile -[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:test -[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.18:test -[INFO] | | +- org.springframework.boot:spring-boot:jar:2.7.18:test -[INFO] | | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | | \- (org.springframework:spring-context:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test -[INFO] | | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.18:test -[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.12:test -[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.2.12:test -[INFO] | | | | \- (org.slf4j:slf4j-api:jar:1.7.32:test - omitted for conflict with 2.0.13) -[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:test -[INFO] | | | | +- (org.slf4j:slf4j-api:jar:1.7.35:test - omitted for conflict with 2.0.13) -[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:test -[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:test -[INFO] | | | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) -[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test -[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- org.yaml:snakeyaml:jar:1.30:test -[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.18:test -[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.5:test -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.5:test -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.5:test -[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.13.5:test - omitted for conflict with 2.17.1) -[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.18:test -[INFO] | | +- (jakarta.annotation:jakarta.annotation-api:jar:1.3.5:test - omitted for duplicate) -[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test -[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.83:test -[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.83:test -[INFO] | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.83:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- org.springframework:spring-webmvc:jar:5.3.31:test -[INFO] | +- org.springframework:spring-aop:jar:5.3.31:test -[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-context:jar:5.3.31:test -[INFO] | | +- (org.springframework:spring-aop:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-beans:jar:5.3.31:test - omitted for duplicate) -[INFO] | | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | | \- (org.springframework:spring-expression:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-expression:jar:5.3.31:test -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- (org.springframework:spring-web:jar:5.3.31:test - omitted for duplicate) -[INFO] +- org.springframework.cloud:spring-cloud-starter-openfeign:jar:3.1.9:test -[INFO] | +- org.springframework.cloud:spring-cloud-starter:jar:3.1.8:test -[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:2.6.15:test - omitted for conflict with 2.7.18) -[INFO] | | +- org.springframework.cloud:spring-cloud-context:jar:3.1.8:test -[INFO] | | | \- (org.springframework.security:spring-security-crypto:jar:5.6.10:test - omitted for duplicate) -[INFO] | | +- (org.springframework.cloud:spring-cloud-commons:jar:3.1.8:test - omitted for duplicate) -[INFO] | | \- org.springframework.security:spring-security-rsa:jar:1.0.12.RELEASE:test -[INFO] | | \- org.bouncycastle:bcpkix-jdk18on:jar:1.73:test -[INFO] | | +- org.bouncycastle:bcprov-jdk18on:jar:1.73:test -[INFO] | | \- org.bouncycastle:bcutil-jdk18on:jar:1.73:test -[INFO] | | \- (org.bouncycastle:bcprov-jdk18on:jar:1.73:test - omitted for duplicate) -[INFO] | +- org.springframework.cloud:spring-cloud-openfeign-core:jar:3.1.9:test -[INFO] | | +- (org.springframework.boot:spring-boot-autoconfigure:jar:2.6.15:test - omitted for conflict with 2.7.18) -[INFO] | | +- org.springframework.boot:spring-boot-starter-aop:jar:2.6.15:test -[INFO] | | | +- (org.springframework.boot:spring-boot-starter:jar:2.6.15:test - omitted for conflict with 2.7.18) -[INFO] | | | +- (org.springframework:spring-aop:jar:5.3.27:test - omitted for conflict with 5.3.31) -[INFO] | | | \- org.aspectj:aspectjweaver:jar:1.9.7:test -[INFO] | | \- (commons-fileupload:commons-fileupload:jar:1.5:test - omitted for duplicate) -[INFO] | +- (org.springframework:spring-web:jar:5.3.27:test - omitted for conflict with 5.3.31) -[INFO] | +- org.springframework.cloud:spring-cloud-commons:jar:3.1.8:test -[INFO] | | \- org.springframework.security:spring-security-crypto:jar:5.6.10:test -[INFO] | +- (io.github.openfeign:feign-core:jar:11.10:test - omitted for conflict with 13.3) -[INFO] | \- io.github.openfeign:feign-slf4j:jar:11.10:test -[INFO] | +- (io.github.openfeign:feign-core:jar:11.10:test - omitted for conflict with 13.3) -[INFO] | \- (org.slf4j:slf4j-api:jar:1.7.36:test - omitted for conflict with 2.0.13) -[INFO] +- org.projectlombok:lombok:jar:1.18.32:provided -[INFO] +- org.slf4j:slf4j-api:jar:2.0.13:compile -[INFO] +- io.github.openfeign:feign-core:jar:13.3:provided -[INFO] +- io.github.openfeign:feign-jackson:jar:13.3:test -[INFO] | +- (io.github.openfeign:feign-core:jar:13.3:test - omitted for duplicate) -[INFO] | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:test -[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.1:test -[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.17.1:test -[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.18:test -[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.18:test -[INFO] | | \- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.18:test -[INFO] | | +- (org.springframework.boot:spring-boot:jar:2.7.18:test - omitted for duplicate) -[INFO] | | +- (org.springframework.boot:spring-boot-test:jar:2.7.18:test - omitted for duplicate) -[INFO] | | \- (org.springframework.boot:spring-boot-autoconfigure:jar:2.7.18:test - omitted for duplicate) -[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test -[INFO] | | +- net.minidev:json-smart:jar:2.4.7:test -[INFO] | | | \- net.minidev:accessors-smart:jar:2.4.7:test -[INFO] | | | \- org.ow2.asm:asm:jar:9.1:test -[INFO] | | \- (org.slf4j:slf4j-api:jar:1.7.33:test - omitted for conflict with 2.0.13) -[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test -[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test -[INFO] | +- (org.assertj:assertj-core:jar:3.22.0:test - omitted for conflict with 3.26.0) -[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test -[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test -[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | | +- (org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | | \- (org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test - omitted for conflict with 5.10.2) -[INFO] | +- (org.mockito:mockito-core:jar:4.5.1:test - omitted for conflict with 5.12.0) -[INFO] | +- (org.mockito:mockito-junit-jupiter:jar:4.5.1:test - omitted for conflict with 5.12.0) -[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.1:test -[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test -[INFO] | +- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | +- org.springframework:spring-test:jar:5.3.31:test -[INFO] | | \- (org.springframework:spring-core:jar:5.3.31:test - omitted for duplicate) -[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.1:test -[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test - omitted for duplicate) -[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test (scope not updated to test) -[INFO] | +- org.junit.platform:junit-platform-engine:jar:1.10.2:test -[INFO] | | +- org.opentest4j:opentest4j:jar:1.3.0:test -[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test -[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test -[INFO] | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate) -[INFO] | | +- (org.junit.platform:junit-platform-commons:jar:1.10.2:test - omitted for duplicate) -[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] | \- org.apiguardian:apiguardian-api:jar:1.1.2:test -[INFO] +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test (scope not updated to test) -[INFO] | +- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) -[INFO] | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) -[INFO] +- org.assertj:assertj-core:jar:3.26.0:test (scope not updated to test) -[INFO] | \- net.bytebuddy:byte-buddy:jar:1.14.16:test -[INFO] +- org.mockito:mockito-core:jar:5.12.0:test (scope not updated to test) -[INFO] | +- (net.bytebuddy:byte-buddy:jar:1.14.15:test - omitted for conflict with 1.14.16) -[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.14.15:test -[INFO] | \- org.objenesis:objenesis:jar:3.3:test -[INFO] +- org.mockito:mockito-junit-jupiter:jar:5.12.0:test (scope not updated to test) -[INFO] | +- (org.mockito:mockito-core:jar:5.12.0:test - omitted for duplicate) -[INFO] | \- (org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test - omitted for duplicate) -[INFO] +- net.jcip:jcip-annotations:jar:1.0:provided -[INFO] \- com.github.spotbugs:spotbugs-annotations:jar:4.8.6:provided -[INFO] \- com.google.code.findbugs:jsr305:jar:3.0.2:provided -[INFO] ------------------------------------------------------------------------ -[INFO] Reactor Summary for Open Feign Forms Parent 4.0.0: -[INFO] -[INFO] Open Feign Forms Parent ............................ SUCCESS [ 0.270 s] -[INFO] Open Feign Forms Core .............................. SUCCESS [ 0.032 s] -[INFO] Open Feign Forms Extension for Spring .............. SUCCESS [ 0.033 s] -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 0.700 s -[INFO] Finished at: 2024-06-25T01:43:33+02:00 -[INFO] ------------------------------------------------------------------------ From d00c80f01b2e881fe8881c4f12567c5708597ee3 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 25 Jun 2024 02:28:48 +0200 Subject: [PATCH 87/93] fix depricated API --- .../spring/converter/SpringManyMultipartFilesReader.java | 4 ++-- .../java/feign/form/MultipartFormContentProcessor.java | 4 +--- .../java/feign/form/UrlencodedFormContentProcessor.java | 7 ++----- .../src/main/java/feign/form/multipart/DelegateWriter.java | 2 +- feign-form/src/test/java/feign/form/BasicClientTest.java | 7 ++++--- .../src/test/java/feign/form/ByteArrayClientTest.java | 3 ++- feign-form/src/test/java/feign/form/CustomClientTest.java | 7 ++++--- feign-form/src/test/java/feign/form/FormPropertyTest.java | 7 ++++--- feign-form/src/test/java/feign/form/WildCardMapTest.java | 4 ++-- 9 files changed, 22 insertions(+), 23 deletions(-) diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index 47762d71e..1bcdccec2 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -119,7 +119,7 @@ protected void writeInternal (MultipartFile[] byteArrayMultipartFiles, HttpOutpu private byte[] getMultiPartBoundary (MediaType contentType) { val boundaryString = unquote(contentType.getParameter("boundary")); - if (StringUtils.isEmpty(boundaryString)) { + if (StringUtils.hasLength(boundaryString) == false) { throw new HttpMessageConversionException("Content-Type missing boundary information."); } return boundaryString.getBytes(UTF_8); @@ -158,7 +158,7 @@ private Map splitIntoKeyValuePairs (String str, Pattern entriesS Pattern keyValueSeparatorPattern, boolean unquoteValue ) { val keyValuePairs = new IgnoreKeyCaseMap(); - if (!StringUtils.isEmpty(str)) { + if (StringUtils.hasLength(str)) { val tokens = entriesSeparatorPattern.split(str); for (val token : tokens) { val pair = keyValueSeparatorPattern.split(token.trim(), 2); diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index ac3bdc593..7a655d910 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -30,7 +30,6 @@ import lombok.experimental.FieldDefaults; import lombok.val; -import feign.Request; import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; @@ -101,8 +100,7 @@ public void process (RequestTemplate template, Charset charset, MapemptyList()); // reset header template.header(CONTENT_TYPE_HEADER, contentTypeValue); - template.body(body); + template.body(bytes, charset); } @Override @@ -97,9 +95,8 @@ private String createKeyValuePair (Entry entry, Charset charset) .toString(); } - @SuppressWarnings("unchecked") private String createKeyValuePairFromCollection (String key, Object values, Charset charset) { - val collection = (Collection) values; + val collection = (Collection) values; val array = collection.toArray(new Object[0]); return createKeyValuePairFromArray(key, array, charset); } diff --git a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java index 5ece90fea..eae6ca2d4 100644 --- a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java @@ -48,7 +48,7 @@ public boolean isApplicable (Object value) { protected void write (Output output, String key, Object value) throws EncodeException { val fake = new RequestTemplate(); delegate.encode(value, value.getClass(), fake); - val bytes = fake.requestBody().asBytes(); + val bytes = fake.body(); val string = new String(bytes, output.getCharset()).replaceAll("\n", ""); parameterWriter.write(output, key, string); } diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index bcbb46424..dc056af7c 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -33,12 +33,13 @@ import org.springframework.boot.test.context.SpringBootTest; import feign.Feign; +import feign.Logger.JavaLogger; import feign.Response; import feign.jackson.JacksonEncoder; @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class + webEnvironment = DEFINED_PORT, + classes = Server.class ) class BasicClientTest { @@ -47,7 +48,7 @@ class BasicClientTest { static { API = Feign.builder() .encoder(new FormEncoder(new JacksonEncoder())) - .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) + .logger(new JavaLogger(BasicClientTest.class).appendToFile("log.txt")) .logLevel(FULL) .target(TestClient.class, "http://localhost:8080"); } diff --git a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java index 222ca9e1b..928ef6d61 100644 --- a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java +++ b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java @@ -26,6 +26,7 @@ import feign.Feign; import feign.Headers; +import feign.Logger.JavaLogger; import feign.Param; import feign.RequestLine; import feign.Response; @@ -44,7 +45,7 @@ class ByteArrayClientTest { API = Feign.builder() .encoder(encoder) - .logger(new feign.Logger.JavaLogger().appendToFile("log-byte.txt")) + .logger(new JavaLogger(ByteArrayClientTest.class).appendToFile("log-byte.txt")) .logLevel(FULL) .target(CustomClient.class, "http://localhost:8080"); } diff --git a/feign-form/src/test/java/feign/form/CustomClientTest.java b/feign-form/src/test/java/feign/form/CustomClientTest.java index 373f731f1..bf73b2ef8 100644 --- a/feign-form/src/test/java/feign/form/CustomClientTest.java +++ b/feign-form/src/test/java/feign/form/CustomClientTest.java @@ -27,6 +27,7 @@ import feign.Feign; import feign.Headers; +import feign.Logger.JavaLogger; import feign.Param; import feign.RequestLine; import feign.codec.EncodeException; @@ -35,8 +36,8 @@ import feign.jackson.JacksonEncoder; @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class + webEnvironment = DEFINED_PORT, + classes = Server.class ) class CustomClientTest { @@ -49,7 +50,7 @@ class CustomClientTest { API = Feign.builder() .encoder(encoder) - .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) + .logger(new JavaLogger(CustomClientTest.class).appendToFile("log.txt")) .logLevel(FULL) .target(CustomClient.class, "http://localhost:8080"); } diff --git a/feign-form/src/test/java/feign/form/FormPropertyTest.java b/feign-form/src/test/java/feign/form/FormPropertyTest.java index 53ef11957..df89b1665 100644 --- a/feign-form/src/test/java/feign/form/FormPropertyTest.java +++ b/feign-form/src/test/java/feign/form/FormPropertyTest.java @@ -26,12 +26,13 @@ import feign.Feign; import feign.Headers; +import feign.Logger.JavaLogger; import feign.RequestLine; import feign.jackson.JacksonEncoder; @SpringBootTest( - webEnvironment = DEFINED_PORT, - classes = Server.class + webEnvironment = DEFINED_PORT, + classes = Server.class ) class FormPropertyTest { @@ -40,7 +41,7 @@ class FormPropertyTest { static { API = Feign.builder() .encoder(new FormEncoder(new JacksonEncoder())) - .logger(new feign.Logger.JavaLogger().appendToFile("log.txt")) + .logger(new JavaLogger(FormPropertyTest.class).appendToFile("log.txt")) .logLevel(FULL) .target(FormClient.class, "http://localhost:8080"); } diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index 38d99ed19..62a9df713 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -29,7 +29,7 @@ import feign.Feign; import feign.Headers; -import feign.Logger; +import feign.Logger.JavaLogger; import feign.RequestLine; import feign.Response; @@ -45,7 +45,7 @@ class WildCardMapTest { static void configureClient () { api = Feign.builder() .encoder(new FormEncoder()) - .logger(new Logger.JavaLogger().appendToFile("log.txt")) + .logger(new JavaLogger(WildCardMapTest.class).appendToFile("log.txt")) .logLevel(FULL) .target(FormUrlEncodedApi.class, "http://localhost:8080"); } From 3c3951d7790384c6f403e1d045d2334e4ed539b7 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 25 Jun 2024 02:30:39 +0200 Subject: [PATCH 88/93] remove useless comment --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index a0cae8821..1cc6b6966 100644 --- a/pom.xml +++ b/pom.xml @@ -207,12 +207,6 @@ limitations under the License. 4.8.6 provided - From 1577990351298d2d8c902e3fbff0c02a5690c00a Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 25 Jun 2024 02:46:10 +0200 Subject: [PATCH 89/93] refactoring --- .../java/feign/form/feign/spring/Server.java | 65 ++++++++--------- .../feign/form/multipart/AbstractWriter.java | 2 - .../java/feign/form/multipart/Output.java | 1 - .../src/test/java/feign/form/Server.java | 72 ++++++++++++------- 4 files changed, 77 insertions(+), 63 deletions(-) diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index b4cce8652..be96dc74c 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -21,13 +21,10 @@ import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; -import static org.springframework.web.bind.annotation.RequestMethod.GET; -import static org.springframework.web.bind.annotation.RequestMethod.POST; import java.io.IOException; import java.util.Map; -import lombok.SneakyThrows; import lombok.val; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @@ -39,9 +36,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; @@ -53,55 +51,53 @@ @SuppressWarnings("checkstyle:DesignForExtension") public class Server { - @RequestMapping( + @PostMapping( path = "/multipart/upload1/{folder}", - method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) - @SneakyThrows - public String upload1 (@PathVariable("folder") String folder, - @RequestPart("file") MultipartFile file, - @RequestParam(value = "message", required = false) String message - ) { + public String upload1 ( + @PathVariable("folder") String folder, + @RequestPart("file") MultipartFile file, + @RequestParam(value = "message", required = false) String message + ) throws IOException { return new String(file.getBytes()) + ':' + message + ':' + folder; } - @RequestMapping( + @PostMapping( path = "/multipart/upload2/{folder}", - method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) - @SneakyThrows - public String upload2 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message - ) { + public String upload2 ( + @RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message + ) throws IOException { return new String(file.getBytes()) + ':' + message + ':' + folder; } - @RequestMapping( + @PostMapping( path = "/multipart/upload3/{folder}", - method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) - public String upload3 (@RequestBody MultipartFile file, - @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message + public String upload3 ( + @RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message ) { return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; } - @RequestMapping(path = "/multipart/upload4/{id}", method = POST) - public String upload4 (@PathVariable("id") String id, - @RequestBody Map map, - @RequestParam String userName + @PostMapping("/multipart/upload4/{id}") + public String upload4 ( + @PathVariable("id") String id, + @RequestBody Map map, + @RequestParam String userName ) { return userName + ':' + id + ':' + map.size(); } - @RequestMapping( + @PostMapping( path = "/multipart/upload5", - method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) void upload5 (Dto dto) throws IOException { @@ -111,13 +107,13 @@ void upload5 (Dto dto) throws IOException { assert "Hello world".equals(new String(dto.getFile().getBytes(), UTF_8)); } - @RequestMapping( + @PostMapping( path = "/multipart/upload6", - method = POST, consumes = MULTIPART_FORM_DATA_VALUE ) - public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa1, - @RequestParam("popa2") MultipartFile popa2 + public ResponseEntity upload6 ( + @RequestParam("popa1") MultipartFile popa1, + @RequestParam("popa2") MultipartFile popa2 ) throws Exception { HttpStatus status = I_AM_A_TEAPOT; String result = ""; @@ -128,9 +124,8 @@ public ResponseEntity upload6 (@RequestParam("popa1") MultipartFile popa return ResponseEntity.status(status).body(result); } - @RequestMapping( + @GetMapping( path = "/multipart/download/{fileId}", - method = GET, produces = MULTIPART_FORM_DATA_VALUE ) public MultiValueMap download (@PathVariable("fileId") String fileId) { diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index 982adf389..def25ad2c 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -20,7 +20,6 @@ import java.net.URLConnection; -import lombok.SneakyThrows; import lombok.val; import feign.codec.EncodeException; @@ -63,7 +62,6 @@ protected void write (Output output, String key, Object value) throws EncodeExce * @param fileName file name. * @param contentType type of file content. May be the {@code null}, in that case it will be determined by file name. */ - @SneakyThrows protected void writeFileMetadata (Output output, String name, String fileName, String contentType) { val contentDespositionBuilder = new StringBuilder() .append("Content-Disposition: form-data; name=\"").append(name).append("\""); diff --git a/feign-form/src/main/java/feign/form/multipart/Output.java b/feign-form/src/main/java/feign/form/multipart/Output.java index 6bcd18149..ddd6be7cc 100644 --- a/feign-form/src/main/java/feign/form/multipart/Output.java +++ b/feign-form/src/main/java/feign/form/multipart/Output.java @@ -75,7 +75,6 @@ public Output write (byte[] bytes) { * * @return this output */ - @SneakyThrows public Output write (byte[] bytes, int offset, int length) { outputStream.write(bytes, offset, length); return this; diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 80147fa70..f206560a2 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -25,7 +25,6 @@ import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; -import static org.springframework.web.bind.annotation.RequestMethod.POST; import java.io.IOException; import java.util.Collection; @@ -36,10 +35,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; @@ -51,9 +50,10 @@ @SuppressWarnings("checkstyle:DesignForExtension") public class Server { - @RequestMapping(path = "/form", method = POST) - public ResponseEntity form (@RequestParam("key1") String key1, - @RequestParam("key2") String key2 + @PostMapping("/form") + public ResponseEntity form ( + @RequestParam("key1") String key1, + @RequestParam("key2") String key2 ) { val status = !key1.equals(key2) ? BAD_REQUEST @@ -61,11 +61,12 @@ public ResponseEntity form (@RequestParam("key1") String key1, return ResponseEntity.status(status).body(null); } - @RequestMapping(path = "/upload/{id}", method = POST) + @PostMapping("/upload/{id}") @ResponseStatus(OK) - public ResponseEntity upload (@PathVariable("id") Integer id, - @RequestParam("public") Boolean isPublic, - @RequestParam("file") MultipartFile file + public ResponseEntity upload ( + @PathVariable("id") Integer id, + @RequestParam("public") Boolean isPublic, + @RequestParam("file") MultipartFile file ) { HttpStatus status; if (id == null || id != 10) { @@ -82,7 +83,7 @@ public ResponseEntity upload (@PathVariable("id") Integer id, return ResponseEntity.status(status).body(file.getSize()); } - @RequestMapping(path = "/upload", method = POST) + @PostMapping("/upload") public ResponseEntity upload (@RequestParam("file") MultipartFile file) { HttpStatus status; if (file.getSize() == 0) { @@ -95,7 +96,7 @@ public ResponseEntity upload (@RequestParam("file") MultipartFile file) { return ResponseEntity.status(status).body(file.getSize()); } - @RequestMapping(path = "/upload/files", method = POST) + @PostMapping("/upload/files") public ResponseEntity upload (@RequestParam("files") MultipartFile[] files) { HttpStatus status; if (files[0].getSize() == 0 || files[1].getSize() == 0) { @@ -109,7 +110,7 @@ public ResponseEntity upload (@RequestParam("files") MultipartFile[] files return ResponseEntity.status(status).body(files[0].getSize() + files[1].getSize()); } - @RequestMapping(path = "/json", method = POST, consumes = APPLICATION_JSON_VALUE) + @PostMapping(path = "/json", consumes = APPLICATION_JSON_VALUE) public ResponseEntity json (@RequestBody Dto dto) { HttpStatus status; if (!dto.getName().equals("Artem")) { @@ -122,16 +123,21 @@ public ResponseEntity json (@RequestBody Dto dto) { return ResponseEntity.status(status).body("ok"); } - @RequestMapping("/query_map") - public ResponseEntity queryMap (@RequestParam("filter") List filters) { + @GetMapping("/query_map") + public ResponseEntity queryMap ( + @RequestParam("filter") List filters + ) { val status = filters != null && !filters.isEmpty() ? OK : I_AM_A_TEAPOT; return ResponseEntity.status(status).body(filters.size()); } - @RequestMapping(path = "/wild-card-map", method = POST, consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity wildCardMap (@RequestParam("key1") String key1, @RequestParam("key2") String key2) { + @PostMapping(path = "/wild-card-map", consumes = APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity wildCardMap ( + @RequestParam("key1") String key1, + @RequestParam("key2") String key2 + ) { val status = key1.equals(key2) ? OK : I_AM_A_TEAPOT; @@ -139,7 +145,9 @@ public ResponseEntity wildCardMap (@RequestParam("key1") String key1, @ } @PostMapping(path = "/upload/with_dto", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadWithDto (Dto dto, @RequestPart("file") MultipartFile file + public ResponseEntity uploadWithDto ( + Dto dto, + @RequestPart("file") MultipartFile file ) throws IOException { val status = dto != null && dto.getName().equals("Artem") ? OK @@ -148,7 +156,9 @@ public ResponseEntity uploadWithDto (Dto dto, @RequestPart("file") Multipa } @PostMapping(path = "/upload/byte_array", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadByteArray (@RequestPart("file") MultipartFile file) { + public ResponseEntity uploadByteArray ( + @RequestPart("file") MultipartFile file + ) { val status = file != null ? OK : I_AM_A_TEAPOT; @@ -159,7 +169,9 @@ public ResponseEntity uploadByteArray (@RequestPart("file") MultipartFil // We just want the request because when there's a filename part of the Content-Disposition header spring // will treat it as a file (available through getFile()) and when it doesn't have the filename part it's // available in the parameter (getParameter()) - public ResponseEntity uploadByteArrayParameter (MultipartHttpServletRequest request) { + public ResponseEntity uploadByteArrayParameter ( + MultipartHttpServletRequest request + ) { val status = request.getFile("file") == null && request.getParameter("file") != null ? OK : I_AM_A_TEAPOT; @@ -167,7 +179,9 @@ public ResponseEntity uploadByteArrayParameter (MultipartHttpServletRequ } @PostMapping(path = "/upload/unknown_type", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadUnknownType (@RequestPart("file") MultipartFile file) { + public ResponseEntity uploadUnknownType ( + @RequestPart("file") MultipartFile file + ) { val status = file != null ? OK : I_AM_A_TEAPOT; @@ -175,7 +189,9 @@ public ResponseEntity uploadUnknownType (@RequestPart("file") MultipartF } @PostMapping(path = "/upload/form_data", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadFormData (@RequestPart("file") MultipartFile file) { + public ResponseEntity uploadFormData ( + @RequestPart("file") MultipartFile file + ) { val status = file != null ? OK : I_AM_A_TEAPOT; @@ -183,7 +199,9 @@ public ResponseEntity uploadFormData (@RequestPart("file") MultipartFile } @PostMapping(path = "/submit/url", consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity submitRepeatableQueryParam (@RequestParam("names") String[] names) { + public ResponseEntity submitRepeatableQueryParam ( + @RequestParam("names") String[] names + ) { val response = new StringBuilder(); if (names != null && names.length == 2) { response @@ -199,7 +217,9 @@ public ResponseEntity submitRepeatableQueryParam (@RequestParam("names") } @PostMapping(path = "/submit/form", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity submitRepeatableFormParam (@RequestParam("names") Collection names) { + public ResponseEntity submitRepeatableFormParam ( + @RequestParam("names") Collection names + ) { val response = new StringBuilder(); if (names != null && names.size() == 2) { val iterator = names.iterator(); @@ -216,8 +236,10 @@ public ResponseEntity submitRepeatableFormParam (@RequestParam("names") } @PostMapping(path = "/form-data", consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity submitPostData (@RequestParam("f_name") String firstName, - @RequestParam("age") Integer age) { + public ResponseEntity submitPostData ( + @RequestParam("f_name") String firstName, + @RequestParam("age") Integer age + ) { val response = new StringBuilder(); if (firstName != null && age != null) { response From 63ba1e91c8b0b144f7ba04f62216e042cf648d23 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 25 Jun 2024 23:39:44 +0200 Subject: [PATCH 90/93] 63 delegation leads to unexpected result (#122) * fix test * fix map argument handling --------- Co-authored-by: Artem Labazin --- feign-form/pom.xml | 1 + .../src/main/java/feign/form/FormEncoder.java | 4 +- .../src/test/java/feign/form/Server.java | 3 +- .../java/feign/form/issues/Issue63Test.java | 76 +++++++++++++ .../java/feign/form/utils/UndertowServer.java | 101 ++++++++++++++++++ pom.xml | 69 ++++++++++++ 6 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 feign-form/src/test/java/feign/form/issues/Issue63Test.java create mode 100644 feign-form/src/test/java/feign/form/utils/UndertowServer.java diff --git a/feign-form/pom.xml b/feign-form/pom.xml index e42c5c252..643eed64d 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -41,6 +41,7 @@ limitations under the License. feign.form + feign.form.multipart diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index c6bb1c266..8d6ab0226 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -88,13 +88,13 @@ public FormEncoder (Encoder delegate) { public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException { String contentTypeValue = getContentTypeValue(template.headers()); val contentType = ContentType.of(contentTypeValue); - if (!processors.containsKey(contentType)) { + if (processors.containsKey(contentType) == false) { delegate.encode(object, bodyType, template); return; } Map data; - if (MAP_STRING_WILDCARD.equals(bodyType)) { + if (object instanceof Map) { data = (Map) object; } else if (isUserPojo(bodyType)) { data = toMap(object); diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index f206560a2..4d81f107a 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -35,7 +35,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -123,7 +122,7 @@ public ResponseEntity json (@RequestBody Dto dto) { return ResponseEntity.status(status).body("ok"); } - @GetMapping("/query_map") + @PostMapping("/query_map") public ResponseEntity queryMap ( @RequestParam("filter") List filters ) { diff --git a/feign-form/src/test/java/feign/form/issues/Issue63Test.java b/feign-form/src/test/java/feign/form/issues/Issue63Test.java new file mode 100644 index 000000000..c0d084f0c --- /dev/null +++ b/feign-form/src/test/java/feign/form/issues/Issue63Test.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 the original author or authors. + * + * 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 feign.form.issues; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; + +import io.undertow.server.HttpServerExchange; +import lombok.val; +import org.junit.jupiter.api.Test; + +import feign.Feign; +import feign.Headers; +import feign.RequestLine; +import feign.form.FormEncoder; +import feign.form.utils.UndertowServer; +import feign.jackson.JacksonEncoder; + +// https://github.com/OpenFeign/feign-form/issues/63 +class Issue63Test { + + @Test + void test () { + try (val server = UndertowServer.builder().callback(this::handleRequest).start()) { + val client = Feign.builder() + .encoder(new FormEncoder(new JacksonEncoder())) + .target(Client.class, server.getConnectUrl()); + + val data = new HashMap(); + data.put("from", "+987654321"); + data.put("to", "+123456789"); + data.put("body", "hello world"); + + assertThat(client.map(data)) + .isEqualTo("ok"); + } + } + + private void handleRequest (HttpServerExchange exchange, byte[] message) { + // assert request + assertThat(exchange.getRequestHeaders().getFirst(io.undertow.util.Headers.CONTENT_TYPE)) + .isEqualTo("application/x-www-form-urlencoded; charset=UTF-8"); + assertThat(message) + .asString() + .isEqualTo("from=%2B987654321&to=%2B123456789&body=hello+world"); + + // build response + exchange.getResponseHeaders() + .put(io.undertow.util.Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender() + .send("ok"); + } + + interface Client { + + @RequestLine("POST") + @Headers("Content-Type: application/x-www-form-urlencoded; charset=utf-8") + String map (Map data); + } +} diff --git a/feign-form/src/test/java/feign/form/utils/UndertowServer.java b/feign-form/src/test/java/feign/form/utils/UndertowServer.java new file mode 100644 index 000000000..2348a8d89 --- /dev/null +++ b/feign-form/src/test/java/feign/form/utils/UndertowServer.java @@ -0,0 +1,101 @@ +/* + * Copyright 2024 the original author or authors. + * + * 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 feign.form.utils; + +import java.net.InetSocketAddress; + +import io.appulse.utils.SocketUtils; +import io.undertow.Undertow; +import io.undertow.io.Receiver.FullBytesCallback; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.BlockingHandler; +import io.undertow.util.Headers; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.val; + +public final class UndertowServer implements AutoCloseable { + + private final Undertow undertow; + + @Builder(buildMethodName = "start") + private UndertowServer (FullBytesCallback callback) { + val port = SocketUtils.findFreePort() + .orElseThrow(() -> new IllegalStateException("no available port to start server")); + + undertow = Undertow.builder() + .addHttpListener(port, "localhost") + .setHandler( + new BlockingHandler( + new ReadAllBytesHandler(callback) + ) + ) + .build(); + + undertow.start(); + } + + /** + * Returns server connect URL. + * + * @return listining server's url. + */ + public String getConnectUrl () { + val listenerInfo = undertow.getListenerInfo() + .iterator() + .next(); + + val address = (InetSocketAddress) listenerInfo.getAddress(); + + return String.format( + "%s://%s:%d", + listenerInfo.getProtcol(), + address.getHostString(), + address.getPort() + ); + } + + @Override + public void close () { + undertow.stop(); + } + + @RequiredArgsConstructor + private static final class ReadAllBytesHandler implements HttpHandler { + + private final FullBytesCallback callback; + + @Override + public void handleRequest (HttpServerExchange exchange) throws Exception { + exchange.getRequestReceiver() + .receiveFullBytes(this::handleBytes); + } + + private void handleBytes (HttpServerExchange exchange, byte[] message) { + try { + callback.handle(exchange, message); + } catch (Throwable ex) { + exchange.setStatusCode(500); + exchange.getResponseHeaders() + .put(Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender() + .send(ex.getMessage()); + } + } + } +} diff --git a/pom.xml b/pom.xml index 1cc6b6966..8ac4085f2 100644 --- a/pom.xml +++ b/pom.xml @@ -162,6 +162,19 @@ limitations under the License. test + + io.undertow + undertow-core + 2.3.14.Final + test + + + io.appulse + utils-java + 1.18.0 + test + + org.junit.jupiter junit-jupiter-engine @@ -402,6 +415,62 @@ limitations under the License. + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + ${skipUnitTests} + false + + **/*Test.java + **/*Tests.java + **/Test*.java + + + **/it/** + **/*IntegrationTest.java + **/*IntegrationTests.java + **/*IT.java + **/IT*.java + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.1.2 + + ${skipIntegrationTests} + false + + **/*IntegrationTest.java + **/*IntegrationTests.java + **/*IT.java + **/IT*.java + **/it/**/*Test.java + **/it/**/*Tests.java + **/it/**/Test*.java + + + ${project.groupId}:${project.artifactId} + + + ${project.build.outputDirectory} + + + + + integration-test + + integration-test + verify + + + + + maven-compiler-plugin 3.13.0 From 291f033fb3a2542118a7dc677435e307f6e184b1 Mon Sep 17 00:00:00 2001 From: Marvin Froeder Date: Sat, 21 Sep 2024 01:13:09 -0300 Subject: [PATCH 91/93] Format sources and fix build --- feign-form-spring/pom.xml | 113 ++++-- .../feign/form/spring/SpringFormEncoder.java | 124 ++++--- .../SpringManyMultipartFilesWriter.java | 80 +++-- .../SpringSingleMultipartFileWriter.java | 57 ++-- .../converter/ByteArrayMultipartFile.java | 71 ++-- .../spring/converter/IgnoreKeyCaseMap.java | 63 ++-- .../SpringManyMultipartFilesReader.java | 279 +++++++-------- feign-form-spring/src/main/java/lombok.config | 1 - .../java/feign/form/feign/spring/Client.java | 113 +++--- .../form/feign/spring/DownloadClient.java | 72 ++-- .../java/feign/form/feign/spring/Dto.java | 29 +- .../java/feign/form/feign/spring/Server.java | 140 ++++---- .../feign/spring/SpringFormEncoderTest.java | 166 +++++---- .../spring/SpringMultipartDecoderTest.java | 57 ++-- .../SpringManyMultipartFilesReaderTest.java | 88 +++-- feign-form/pom.xml | 129 +++++-- .../java/feign/form/ContentProcessor.java | 79 ++--- .../src/main/java/feign/form/ContentType.java | 93 +++-- .../src/main/java/feign/form/FormData.java | 30 +- .../src/main/java/feign/form/FormEncoder.java | 208 ++++++------ .../main/java/feign/form/FormProperty.java | 28 +- .../form/MultipartFormContentProcessor.java | 232 ++++++------- .../form/UrlencodedFormContentProcessor.java | 175 +++++----- .../feign/form/multipart/AbstractWriter.java | 129 ++++--- .../feign/form/multipart/ByteArrayWriter.java | 39 +-- .../feign/form/multipart/DelegateWriter.java | 49 ++- .../feign/form/multipart/FormDataWriter.java | 40 +-- .../feign/form/multipart/ManyFilesWriter.java | 85 +++-- .../form/multipart/ManyParametersWriter.java | 82 +++-- .../java/feign/form/multipart/Output.java | 128 ++++--- .../java/feign/form/multipart/PojoWriter.java | 78 ++--- .../form/multipart/SingleFileWriter.java | 65 ++-- .../form/multipart/SingleParameterWriter.java | 49 ++- .../java/feign/form/multipart/Writer.java | 63 ++-- .../main/java/feign/form/util/PojoUtil.java | 111 +++--- feign-form/src/main/java/lombok.config | 1 - .../test/java/feign/form/BasicClientTest.java | 252 +++++++------- .../java/feign/form/ByteArrayClientTest.java | 56 ++- .../java/feign/form/CustomClientTest.java | 72 ++-- feign-form/src/test/java/feign/form/Dto.java | 27 +- .../src/test/java/feign/form/FormDto.java | 26 +- .../java/feign/form/FormPropertyTest.java | 53 ++- .../src/test/java/feign/form/Server.java | 321 +++++++++--------- .../src/test/java/feign/form/TestClient.java | 98 +++--- .../test/java/feign/form/WildCardMapTest.java | 90 +++-- .../java/feign/form/issues/Issue63Test.java | 75 ++-- .../java/feign/form/utils/UndertowServer.java | 123 ++++--- spring4/pom.xml | 2 +- 48 files changed, 2249 insertions(+), 2292 deletions(-) delete mode 100644 feign-form-spring/src/main/java/lombok.config delete mode 100644 feign-form/src/main/java/lombok.config diff --git a/feign-form-spring/pom.xml b/feign-form-spring/pom.xml index ca63ced4a..6f2e17c1b 100644 --- a/feign-form-spring/pom.xml +++ b/feign-form-spring/pom.xml @@ -1,41 +1,46 @@ - + 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. - 4.0.0 +--> + - feign-form-spring + 4.0.0 - io.github.openfeign - parent - 13.5-SNAPSHOT + io.github.openfeign + parent + 13.5-SNAPSHOT + feign-form-spring + Open Feign Forms Extension for Spring - - 17 - + + + 17 + ${project.basedir}/.. + + + org.projectlombok + lombok + 1.18.34 + provided + + ${project.groupId} feign-form @@ -79,6 +84,68 @@ limitations under the License. + + + io.github.openfeign + feign-jackson + test + + + + org.springframework.boot + spring-boot-starter-web + 2.7.18 + test + + + org.springframework.boot + spring-boot-starter-test + 2.7.18 + test + + + + io.undertow + undertow-core + 2.3.14.Final + test + + + io.appulse + utils-java + 1.18.0 + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + org.assertj + assertj-core + test + + + + org.mockito + mockito-core + 5.12.0 + test + + + org.mockito + mockito-junit-jupiter + 5.12.0 + test + diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java index 16a081f8e..a9c211c72 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringFormEncoder.java @@ -1,30 +1,24 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.spring; import static feign.form.ContentType.MULTIPART; import static java.util.Collections.singletonMap; - import java.lang.reflect.Type; import java.util.HashMap; - import lombok.val; import org.springframework.web.multipart.MultipartFile; - import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; @@ -39,59 +33,59 @@ */ public class SpringFormEncoder extends FormEncoder { - /** - * Constructor with the default Feign's encoder as a delegate. - */ - public SpringFormEncoder() { - this(new Encoder.Default()); - } + /** + * Constructor with the default Feign's encoder as a delegate. + */ + public SpringFormEncoder() { + this(new Encoder.Default()); + } - /** - * Constructor with specified delegate encoder. - * - * @param delegate - * delegate encoder, if this encoder couldn't encode object. - */ - public SpringFormEncoder(Encoder delegate) { - super(delegate); + /** + * Constructor with specified delegate encoder. + * + * @param delegate delegate encoder, if this encoder couldn't encode object. + */ + public SpringFormEncoder(Encoder delegate) { + super(delegate); - val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART); - processor.addFirstWriter(new SpringSingleMultipartFileWriter()); - processor.addFirstWriter(new SpringManyMultipartFilesWriter()); - } + val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART); + processor.addFirstWriter(new SpringSingleMultipartFileWriter()); + processor.addFirstWriter(new SpringManyMultipartFilesWriter()); + } - @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - if (bodyType.equals(MultipartFile[].class)) { - val files = (MultipartFile[]) object; - val data = new HashMap(files.length, 1.F); - for (val file : files) { - data.put(file.getName(), file); - } - super.encode(data, MAP_STRING_WILDCARD, template); - } else if (bodyType.equals(MultipartFile.class)) { - val file = (MultipartFile) object; - val data = singletonMap(file.getName(), object); - super.encode(data, MAP_STRING_WILDCARD, template); - } else if (isMultipartFileCollection(object)) { - val iterable = (Iterable) object; - val data = new HashMap(); - for (val item : iterable) { - val file = (MultipartFile) item; - data.put(file.getName(), file); - } - super.encode(data, MAP_STRING_WILDCARD, template); - } else { - super.encode(object, bodyType, template); - } - } + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) + throws EncodeException { + if (bodyType.equals(MultipartFile[].class)) { + val files = (MultipartFile[]) object; + val data = new HashMap(files.length, 1.F); + for (val file : files) { + data.put(file.getName(), file); + } + super.encode(data, MAP_STRING_WILDCARD, template); + } else if (bodyType.equals(MultipartFile.class)) { + val file = (MultipartFile) object; + val data = singletonMap(file.getName(), object); + super.encode(data, MAP_STRING_WILDCARD, template); + } else if (isMultipartFileCollection(object)) { + val iterable = (Iterable) object; + val data = new HashMap(); + for (val item : iterable) { + val file = (MultipartFile) item; + data.put(file.getName(), file); + } + super.encode(data, MAP_STRING_WILDCARD, template); + } else { + super.encode(object, bodyType, template); + } + } - private boolean isMultipartFileCollection(Object object) { - if (!(object instanceof Iterable)) { - return false; - } - val iterable = (Iterable) object; - val iterator = iterable.iterator(); - return iterator.hasNext() && iterator.next() instanceof MultipartFile; - } + private boolean isMultipartFileCollection(Object object) { + if (!(object instanceof Iterable)) { + return false; + } + val iterable = (Iterable) object; + val iterator = iterable.iterator(); + return iterator.hasNext() && iterator.next() instanceof MultipartFile; + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java index bd25b1f3b..593f124c8 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringManyMultipartFilesWriter.java @@ -1,27 +1,22 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.spring; import static lombok.AccessLevel.PRIVATE; - import lombok.experimental.FieldDefaults; import lombok.val; import org.springframework.web.multipart.MultipartFile; - import feign.codec.EncodeException; import feign.form.multipart.AbstractWriter; import feign.form.multipart.Output; @@ -34,35 +29,36 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class SpringManyMultipartFilesWriter extends AbstractWriter { - SpringSingleMultipartFileWriter fileWriter = new SpringSingleMultipartFileWriter(); + SpringSingleMultipartFileWriter fileWriter = new SpringSingleMultipartFileWriter(); - @Override - public boolean isApplicable(Object value) { - if (value instanceof MultipartFile[]) { - return true; - } - if (!(value instanceof Iterable)) { - return false; - } - val iterable = (Iterable) value; - val iterator = iterable.iterator(); - return iterator.hasNext() && iterator.next() instanceof MultipartFile; - } + @Override + public boolean isApplicable(Object value) { + if (value instanceof MultipartFile[]) { + return true; + } + if (!(value instanceof Iterable)) { + return false; + } + val iterable = (Iterable) value; + val iterator = iterable.iterator(); + return iterator.hasNext() && iterator.next() instanceof MultipartFile; + } - @Override - public void write(Output output, String boundary, String key, Object value) throws EncodeException { - if (value instanceof MultipartFile[]) { - val files = (MultipartFile[]) value; - for (val file : files) { - fileWriter.write(output, boundary, key, file); - } - } else if (value instanceof Iterable) { - val iterable = (Iterable) value; - for (val file : iterable) { - fileWriter.write(output, boundary, key, file); - } - } else { - throw new IllegalArgumentException(); - } - } + @Override + public void write(Output output, String boundary, String key, Object value) + throws EncodeException { + if (value instanceof MultipartFile[]) { + val files = (MultipartFile[]) value; + for (val file : files) { + fileWriter.write(output, boundary, key, file); + } + } else if (value instanceof Iterable) { + val iterable = (Iterable) value; + for (val file : iterable) { + fileWriter.write(output, boundary, key, file); + } + } else { + throw new IllegalArgumentException(); + } + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java index db4b31c27..3ed266959 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java +++ b/feign-form-spring/src/main/java/feign/form/spring/SpringSingleMultipartFileWriter.java @@ -1,26 +1,21 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.spring; import java.io.IOException; - import lombok.val; import org.springframework.web.multipart.MultipartFile; - import feign.codec.EncodeException; import feign.form.multipart.AbstractWriter; import feign.form.multipart.Output; @@ -32,22 +27,22 @@ */ public class SpringSingleMultipartFileWriter extends AbstractWriter { - @Override - public boolean isApplicable(Object value) { - return value instanceof MultipartFile; - } - - @Override - protected void write(Output output, String key, Object value) throws EncodeException { - val file = (MultipartFile) value; - writeFileMetadata(output, key, file.getOriginalFilename(), file.getContentType()); - - byte[] bytes; - try { - bytes = file.getBytes(); - } catch (IOException ex) { - throw new EncodeException("Getting multipart file's content bytes error", ex); - } - output.write(bytes); - } + @Override + public boolean isApplicable(Object value) { + return value instanceof MultipartFile; + } + + @Override + protected void write(Output output, String key, Object value) throws EncodeException { + val file = (MultipartFile) value; + writeFileMetadata(output, key, file.getOriginalFilename(), file.getContentType()); + + byte[] bytes; + try { + bytes = file.getBytes(); + } catch (IOException ex) { + throw new EncodeException("Getting multipart file's content bytes error", ex); + } + output.write(bytes); + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java index 599d89fa5..2950620ed 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/ByteArrayMultipartFile.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.spring.converter; import java.io.ByteArrayInputStream; @@ -21,47 +18,45 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; - import lombok.NonNull; import lombok.Value; -import lombok.val; import org.springframework.web.multipart.MultipartFile; /** - * Straight-forward implementation of interface {@link MultipartFile} where the - * file data is held as a byte array in memory. + * Straight-forward implementation of interface {@link MultipartFile} where the file data is held as + * a byte array in memory. */ @Value class ByteArrayMultipartFile implements MultipartFile { - String name; + String name; - String originalFilename; + String originalFilename; - String contentType; + String contentType; - @NonNull - byte[] bytes; + @NonNull + byte[] bytes; - @Override - public boolean isEmpty() { - return bytes.length == 0; - } + @Override + public boolean isEmpty() { + return bytes.length == 0; + } - @Override - public long getSize() { - return bytes.length; - } + @Override + public long getSize() { + return bytes.length; + } - @Override - public InputStream getInputStream() { - return new ByteArrayInputStream(bytes); - } + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(bytes); + } - @Override - public void transferTo(File destination) throws IOException { - try (val outputStream = new FileOutputStream(destination)) { - outputStream.write(bytes); - } - } + @Override + public void transferTo(File destination) throws IOException { + try (var outputStream = new FileOutputStream(destination)) { + outputStream.write(bytes); + } + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java index 7ea31d97d..41e1b0615 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/IgnoreKeyCaseMap.java @@ -1,53 +1,50 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.spring.converter; import java.util.HashMap; import java.util.Locale; /** - * A Map implementation that normalizes the key to UPPER CASE, so that value - * retrieval via the key is case insensitive. + * A Map implementation that normalizes the key to UPPER CASE, so that value retrieval via the key + * is case insensitive. */ final class IgnoreKeyCaseMap extends HashMap { - private static final long serialVersionUID = -2321516556941546746L; + private static final long serialVersionUID = -2321516556941546746L; - private static String normalizeKey(Object key) { - return key == null ? null : key.toString().toUpperCase(new Locale("en_US")); - } + private static String normalizeKey(Object key) { + return key == null ? null : key.toString().toUpperCase(new Locale("en_US")); + } - @Override - public boolean containsKey(Object key) { - return super.containsKey(normalizeKey(key)); - } + @Override + public boolean containsKey(Object key) { + return super.containsKey(normalizeKey(key)); + } - @Override - public String get(Object key) { - return super.get(normalizeKey(key)); - } + @Override + public String get(Object key) { + return super.get(normalizeKey(key)); + } - @Override - public String put(String key, String value) { - return super.put(normalizeKey(key), value); - } + @Override + public String put(String key, String value) { + return super.put(normalizeKey(key), value); + } - @Override - public String remove(Object key) { - return super.remove(normalizeKey(key)); - } + @Override + public String remove(Object key) { + return super.remove(normalizeKey(key)); + } } diff --git a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java index 2e649a8cc..9b6748b1a 100644 --- a/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java +++ b/feign-form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.spring.converter; import static java.nio.charset.StandardCharsets.UTF_8; @@ -21,13 +18,11 @@ import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.LinkedList; import java.util.Map; import java.util.regex.Pattern; - import lombok.experimental.FieldDefaults; import lombok.val; import org.apache.commons.fileupload.MultipartStream; @@ -43,134 +38,142 @@ import org.springframework.web.multipart.MultipartFile; /** - * Implementation of {@link HttpMessageConverter} that can read - * multipart/form-data HTTP bodies (writing is not handled because that is - * already supported by {@link FormHttpMessageConverter}). + * Implementation of {@link HttpMessageConverter} that can read multipart/form-data HTTP bodies + * (writing is not handled because that is already supported by {@link FormHttpMessageConverter}). *

- * This reader supports an array of {@link MultipartFile} as the mapping return - * class type - each multipart body is read into an underlying byte array (in - * memory) implemented via {@link ByteArrayMultipartFile}. + * This reader supports an array of {@link MultipartFile} as the mapping return class type - each + * multipart body is read into an underlying byte array (in memory) implemented via + * {@link ByteArrayMultipartFile}. */ @FieldDefaults(level = PRIVATE, makeFinal = true) public class SpringManyMultipartFilesReader extends AbstractHttpMessageConverter { - private static final Pattern NEWLINES_PATTERN = Pattern.compile("\\R"); - - private static final Pattern COLON_PATTERN = Pattern.compile(":"); - - private static final Pattern SEMICOLON_PATTERN = Pattern.compile(";"); - - private static final Pattern EQUALITY_SIGN_PATTERN = Pattern.compile("="); - - int bufSize; - - /** - * Construct an {@code AbstractHttpMessageConverter} that can read - * mulitpart/form-data. - * - * @param bufSize - * The size of the buffer (in bytes) to read the HTTP multipart body. - */ - public SpringManyMultipartFilesReader(int bufSize) { - super(MULTIPART_FORM_DATA); - this.bufSize = bufSize; - } - - @Override - protected boolean canWrite(MediaType mediaType) { - return false; // Class NOT meant for writing multipart/form-data HTTP bodies - } - - @Override - protected boolean supports(Class clazz) { - return MultipartFile[].class == clazz; - } - - @Override - protected MultipartFile[] readInternal(Class clazz, HttpInputMessage inputMessage) - throws IOException { - val headers = inputMessage.getHeaders(); - if (headers == null) { - throw new HttpMessageNotReadableException("There are no headers at all.", inputMessage); - } - - MediaType contentType = headers.getContentType(); - if (contentType == null) { - throw new HttpMessageNotReadableException("Content-Type is missing.", inputMessage); - } - - val boundaryBytes = getMultiPartBoundary(contentType); - MultipartStream multipartStream = new MultipartStream(inputMessage.getBody(), boundaryBytes, bufSize, null); - - val multiparts = new LinkedList(); - for (boolean nextPart = multipartStream.skipPreamble(); nextPart; nextPart = multipartStream.readBoundary()) { - ByteArrayMultipartFile multiPart; - try { - multiPart = readMultiPart(multipartStream); - } catch (Exception e) { - throw new HttpMessageNotReadableException("Multipart body could not be read.", e, inputMessage); - } - multiparts.add(multiPart); - } - return multiparts.toArray(new ByteArrayMultipartFile[0]); - } - - @Override - protected void writeInternal(MultipartFile[] byteArrayMultipartFiles, HttpOutputMessage outputMessage) { - throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support writing to HTTP body."); - } - - private byte[] getMultiPartBoundary(MediaType contentType) { - val boundaryString = unquote(contentType.getParameter("boundary")); - if (StringUtils.hasLength(boundaryString) == false) { - throw new HttpMessageConversionException("Content-Type missing boundary information."); - } - return boundaryString.getBytes(UTF_8); - } - - private ByteArrayMultipartFile readMultiPart(MultipartStream multipartStream) throws IOException { - val multiPartHeaders = splitIntoKeyValuePairs(multipartStream.readHeaders(), NEWLINES_PATTERN, COLON_PATTERN, - false); - - val contentDisposition = splitIntoKeyValuePairs(multiPartHeaders.get(CONTENT_DISPOSITION), SEMICOLON_PATTERN, - EQUALITY_SIGN_PATTERN, true); - - if (!contentDisposition.containsKey("form-data")) { - throw new HttpMessageConversionException("Content-Disposition is not of type form-data."); - } - - val bodyStream = new ByteArrayOutputStream(); - multipartStream.readBodyData(bodyStream); - return new ByteArrayMultipartFile(contentDisposition.get("name"), contentDisposition.get("filename"), - multiPartHeaders.get(CONTENT_TYPE), bodyStream.toByteArray()); - } - - private Map splitIntoKeyValuePairs(String str, Pattern entriesSeparatorPattern, - Pattern keyValueSeparatorPattern, boolean unquoteValue) { - val keyValuePairs = new IgnoreKeyCaseMap(); - if (StringUtils.hasLength(str)) { - val tokens = entriesSeparatorPattern.split(str); - for (val token : tokens) { - val pair = keyValueSeparatorPattern.split(token.trim(), 2); - val key = pair[0].trim(); - val value = pair.length > 1 ? pair[1].trim() : ""; - - keyValuePairs.put(key, unquoteValue ? unquote(value) : value); - } - } - return keyValuePairs; - } - - private String unquote(String value) { - if (value == null) { - return null; - } - return isSurroundedBy(value, "\"") || isSurroundedBy(value, "'") - ? value.substring(1, value.length() - 1) - : value; - } - - private boolean isSurroundedBy(String value, String preSuffix) { - return value.length() > 1 && value.startsWith(preSuffix) && value.endsWith(preSuffix); - } + private static final Pattern NEWLINES_PATTERN = Pattern.compile("\\R"); + + private static final Pattern COLON_PATTERN = Pattern.compile(":"); + + private static final Pattern SEMICOLON_PATTERN = Pattern.compile(";"); + + private static final Pattern EQUALITY_SIGN_PATTERN = Pattern.compile("="); + + int bufSize; + + /** + * Construct an {@code AbstractHttpMessageConverter} that can read mulitpart/form-data. + * + * @param bufSize The size of the buffer (in bytes) to read the HTTP multipart body. + */ + public SpringManyMultipartFilesReader(int bufSize) { + super(MULTIPART_FORM_DATA); + this.bufSize = bufSize; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; // Class NOT meant for writing multipart/form-data HTTP bodies + } + + @Override + protected boolean supports(Class clazz) { + return MultipartFile[].class == clazz; + } + + @Override + protected MultipartFile[] readInternal(Class clazz, + HttpInputMessage inputMessage) + throws IOException { + val headers = inputMessage.getHeaders(); + if (headers == null) { + throw new HttpMessageNotReadableException("There are no headers at all.", inputMessage); + } + + MediaType contentType = headers.getContentType(); + if (contentType == null) { + throw new HttpMessageNotReadableException("Content-Type is missing.", inputMessage); + } + + val boundaryBytes = getMultiPartBoundary(contentType); + MultipartStream multipartStream = + new MultipartStream(inputMessage.getBody(), boundaryBytes, bufSize, null); + + val multiparts = new LinkedList(); + for (boolean nextPart = multipartStream.skipPreamble(); nextPart; nextPart = + multipartStream.readBoundary()) { + ByteArrayMultipartFile multiPart; + try { + multiPart = readMultiPart(multipartStream); + } catch (Exception e) { + throw new HttpMessageNotReadableException("Multipart body could not be read.", e, + inputMessage); + } + multiparts.add(multiPart); + } + return multiparts.toArray(new ByteArrayMultipartFile[0]); + } + + @Override + protected void writeInternal(MultipartFile[] byteArrayMultipartFiles, + HttpOutputMessage outputMessage) { + throw new UnsupportedOperationException( + getClass().getSimpleName() + " does not support writing to HTTP body."); + } + + private byte[] getMultiPartBoundary(MediaType contentType) { + val boundaryString = unquote(contentType.getParameter("boundary")); + if (StringUtils.hasLength(boundaryString) == false) { + throw new HttpMessageConversionException("Content-Type missing boundary information."); + } + return boundaryString.getBytes(UTF_8); + } + + private ByteArrayMultipartFile readMultiPart(MultipartStream multipartStream) throws IOException { + val multiPartHeaders = + splitIntoKeyValuePairs(multipartStream.readHeaders(), NEWLINES_PATTERN, COLON_PATTERN, + false); + + val contentDisposition = + splitIntoKeyValuePairs(multiPartHeaders.get(CONTENT_DISPOSITION), SEMICOLON_PATTERN, + EQUALITY_SIGN_PATTERN, true); + + if (!contentDisposition.containsKey("form-data")) { + throw new HttpMessageConversionException("Content-Disposition is not of type form-data."); + } + + val bodyStream = new ByteArrayOutputStream(); + multipartStream.readBodyData(bodyStream); + return new ByteArrayMultipartFile(contentDisposition.get("name"), + contentDisposition.get("filename"), + multiPartHeaders.get(CONTENT_TYPE), bodyStream.toByteArray()); + } + + private Map splitIntoKeyValuePairs(String str, + Pattern entriesSeparatorPattern, + Pattern keyValueSeparatorPattern, + boolean unquoteValue) { + val keyValuePairs = new IgnoreKeyCaseMap(); + if (StringUtils.hasLength(str)) { + val tokens = entriesSeparatorPattern.split(str); + for (val token : tokens) { + val pair = keyValueSeparatorPattern.split(token.trim(), 2); + val key = pair[0].trim(); + val value = pair.length > 1 ? pair[1].trim() : ""; + + keyValuePairs.put(key, unquoteValue ? unquote(value) : value); + } + } + return keyValuePairs; + } + + private String unquote(String value) { + if (value == null) { + return null; + } + return isSurroundedBy(value, "\"") || isSurroundedBy(value, "'") + ? value.substring(1, value.length() - 1) + : value; + } + + private boolean isSurroundedBy(String value, String preSuffix) { + return value.length() > 1 && value.startsWith(preSuffix) && value.endsWith(preSuffix); + } } diff --git a/feign-form-spring/src/main/java/lombok.config b/feign-form-spring/src/main/java/lombok.config deleted file mode 100644 index 26f5d95a3..000000000 --- a/feign-form-spring/src/main/java/lombok.config +++ /dev/null @@ -1 +0,0 @@ -lombok.extern.findbugs.addSuppressFBWarnings=true diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java index b3e149aa3..b7009fa9c 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Client.java @@ -1,28 +1,23 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.feign.spring; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.POST; - import java.util.List; import java.util.Map; - import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; @@ -35,53 +30,61 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; - import feign.Logger; import feign.Response; import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; -@FeignClient(name = "multipart-support-service", url = "http://localhost:8080", configuration = Client.ClientConfiguration.class) +@FeignClient(name = "multipart-support-service", url = "http://localhost:8080", + configuration = Client.ClientConfiguration.class) interface Client { - @RequestMapping(value = "/multipart/upload1/{folder}", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) - String upload1(@PathVariable("folder") String folder, @RequestPart("file") MultipartFile file, - @RequestParam(name = "message", required = false) String message); - - @RequestMapping(value = "/multipart/upload2/{folder}", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) - String upload2(@RequestBody MultipartFile file, @PathVariable("folder") String folder, - @RequestParam(name = "message", required = false) String message); - - @RequestMapping(value = "/multipart/upload3/{folder}", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) - String upload3(@RequestBody MultipartFile file, @PathVariable("folder") String folder, - @RequestParam(name = "message", required = false) String message); - - @RequestMapping(path = "/multipart/upload4/{id}", method = POST, produces = APPLICATION_JSON_VALUE) - String upload4(@PathVariable("id") String id, @RequestBody Map map, - @RequestParam("userName") String userName); - - @RequestMapping(path = "/multipart/upload5", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) - Response upload5(Dto dto); - - @RequestMapping(path = "/multipart/upload6", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) - String upload6Array(MultipartFile[] files); - - @RequestMapping(path = "/multipart/upload6", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) - String upload6Collection(List files); - - class ClientConfiguration { - - @Autowired - private ObjectFactory messageConverters; - - @Bean - Encoder feignEncoder() { - return new SpringFormEncoder(new SpringEncoder(messageConverters)); - } - - @Bean - Logger.Level feignLogger() { - return Logger.Level.FULL; - } - } + @RequestMapping(value = "/multipart/upload1/{folder}", method = POST, + consumes = MULTIPART_FORM_DATA_VALUE) + String upload1(@PathVariable("folder") String folder, + @RequestPart("file") MultipartFile file, + @RequestParam(name = "message", required = false) String message); + + @RequestMapping(value = "/multipart/upload2/{folder}", method = POST, + consumes = MULTIPART_FORM_DATA_VALUE) + String upload2(@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(name = "message", required = false) String message); + + @RequestMapping(value = "/multipart/upload3/{folder}", method = POST, + consumes = MULTIPART_FORM_DATA_VALUE) + String upload3(@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(name = "message", required = false) String message); + + @RequestMapping(path = "/multipart/upload4/{id}", method = POST, + produces = APPLICATION_JSON_VALUE) + String upload4(@PathVariable("id") String id, + @RequestBody Map map, + @RequestParam("userName") String userName); + + @RequestMapping(path = "/multipart/upload5", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) + Response upload5(Dto dto); + + @RequestMapping(path = "/multipart/upload6", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) + String upload6Array(MultipartFile[] files); + + @RequestMapping(path = "/multipart/upload6", method = POST, consumes = MULTIPART_FORM_DATA_VALUE) + String upload6Collection(List files); + + class ClientConfiguration { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + Encoder feignEncoder() { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } + + @Bean + Logger.Level feignLogger() { + return Logger.Level.FULL; + } + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java index da568f636..b56f9a02a 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/DownloadClient.java @@ -1,23 +1,19 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.feign.spring; import java.util.ArrayList; - import lombok.val; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -29,44 +25,44 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; - import feign.Logger; import feign.codec.Decoder; import feign.form.spring.converter.SpringManyMultipartFilesReader; -@FeignClient(name = "multipart-download-support-service", url = "http://localhost:8081", configuration = DownloadClient.ClientConfiguration.class) +@FeignClient(name = "multipart-download-support-service", url = "http://localhost:8081", + configuration = DownloadClient.ClientConfiguration.class) interface DownloadClient { - @RequestMapping("/multipart/download/{fileId}") - MultipartFile[] download(@PathVariable("fileId") String fileId); + @RequestMapping("/multipart/download/{fileId}") + MultipartFile[] download(@PathVariable("fileId") String fileId); - class ClientConfiguration { + class ClientConfiguration { - @Autowired - private ObjectFactory messageConverters; + @Autowired + private ObjectFactory messageConverters; - @Bean - Decoder feignDecoder() { - val springConverters = messageConverters.getObject().getConverters(); - val decoderConverters = new ArrayList>(springConverters.size() + 1); + @Bean + Decoder feignDecoder() { + val springConverters = messageConverters.getObject().getConverters(); + val decoderConverters = new ArrayList>(springConverters.size() + 1); - decoderConverters.addAll(springConverters); - decoderConverters.add(new SpringManyMultipartFilesReader(4096)); + decoderConverters.addAll(springConverters); + decoderConverters.add(new SpringManyMultipartFilesReader(4096)); - val httpMessageConverters = new HttpMessageConverters(decoderConverters); + val httpMessageConverters = new HttpMessageConverters(decoderConverters); - return new SpringDecoder(new ObjectFactory() { + return new SpringDecoder(new ObjectFactory() { - @Override - public HttpMessageConverters getObject() { - return httpMessageConverters; - } - }); - } + @Override + public HttpMessageConverters getObject() { + return httpMessageConverters; + } + }); + } - @Bean - Logger.Level feignLoggerLevel() { - return Logger.Level.FULL; - } - } + @Bean + Logger.Level feignLoggerLevel() { + return Logger.Level.FULL; + } + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java index 4255efd6d..2562bf2d0 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Dto.java @@ -1,25 +1,20 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.feign.spring; import static lombok.AccessLevel.PRIVATE; - import java.io.Serializable; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -32,11 +27,11 @@ @FieldDefaults(level = PRIVATE) public class Dto implements Serializable { - private static final long serialVersionUID = -4218390863359894943L; + private static final long serialVersionUID = -4218390863359894943L; - String field1; + String field1; - int field2; + int field2; - MultipartFile file; + MultipartFile file; } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java index 20cf36072..841fc0360 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/Server.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.feign.spring; import static java.nio.charset.StandardCharsets.UTF_8; @@ -21,10 +18,8 @@ import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; - import java.io.IOException; import java.util.Map; - import lombok.val; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @@ -51,31 +46,37 @@ @SuppressWarnings("checkstyle:DesignForExtension") public class Server { - @PostMapping(path = "/multipart/upload1/{folder}", consumes = MULTIPART_FORM_DATA_VALUE) - public String upload1(@PathVariable("folder") String folder, @RequestPart("file") MultipartFile file, - @RequestParam(value = "message", required = false) String message) throws IOException { - return new String(file.getBytes()) + ':' + message + ':' + folder; - } - - @PostMapping(path = "/multipart/upload2/{folder}", consumes = MULTIPART_FORM_DATA_VALUE) - public String upload2(@RequestBody MultipartFile file, @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message) throws IOException { - return new String(file.getBytes()) + ':' + message + ':' + folder; - } - - @PostMapping(path = "/multipart/upload3/{folder}", consumes = MULTIPART_FORM_DATA_VALUE) - public String upload3(@RequestBody MultipartFile file, @PathVariable("folder") String folder, - @RequestParam(value = "message", required = false) String message) { - return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; - } - - @PostMapping("/multipart/upload4/{id}") - public String upload4(@PathVariable("id") String id, @RequestBody Map map, - @RequestParam String userName) { - return userName + ':' + id + ':' + map.size(); - } - - @PostMapping(path = "/multipart/upload5", consumes = MULTIPART_FORM_DATA_VALUE) + @PostMapping(path = "/multipart/upload1/{folder}", consumes = MULTIPART_FORM_DATA_VALUE) + public String upload1(@PathVariable("folder") String folder, + @RequestPart("file") MultipartFile file, + @RequestParam(value = "message", required = false) String message) + throws IOException { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } + + @PostMapping(path = "/multipart/upload2/{folder}", consumes = MULTIPART_FORM_DATA_VALUE) + public String upload2(@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message) + throws IOException { + return new String(file.getBytes()) + ':' + message + ':' + folder; + } + + @PostMapping(path = "/multipart/upload3/{folder}", consumes = MULTIPART_FORM_DATA_VALUE) + public String upload3(@RequestBody MultipartFile file, + @PathVariable("folder") String folder, + @RequestParam(value = "message", required = false) String message) { + return file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder; + } + + @PostMapping("/multipart/upload4/{id}") + public String upload4(@PathVariable("id") String id, + @RequestBody Map map, + @RequestParam String userName) { + return userName + ':' + id + ':' + map.size(); + } + + @PostMapping(path = "/multipart/upload5", consumes = MULTIPART_FORM_DATA_VALUE) void upload5(Dto dto) throws IOException { assert "field 1 value".equals(dto.getField1()); assert 42 == dto.getField2(); @@ -83,35 +84,36 @@ void upload5(Dto dto) throws IOException { assert "Hello world".equals(new String(dto.getFile().getBytes(), UTF_8)); } - @PostMapping(path = "/multipart/upload6", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity upload6(@RequestParam("popa1") MultipartFile popa1, - @RequestParam("popa2") MultipartFile popa2) throws Exception { - HttpStatus status = I_AM_A_TEAPOT; - String result = ""; - if (popa1 != null && popa2 != null) { - status = OK; - result = new String(popa1.getBytes()) + new String(popa2.getBytes()); - } - return ResponseEntity.status(status).body(result); - } - - @GetMapping(path = "/multipart/download/{fileId}", produces = MULTIPART_FORM_DATA_VALUE) - public MultiValueMap download(@PathVariable("fileId") String fileId) { - val multiParts = new LinkedMultiValueMap(); - - val infoString = "The text for file ID " + fileId + ". Testing unicode €"; - val infoPartheader = new HttpHeaders(); - infoPartheader.setContentType(new MediaType("text", "plain", UTF_8)); - - val infoPart = new HttpEntity(infoString, infoPartheader); - - val file = new ClassPathResource("testfile.txt"); - val filePartheader = new HttpHeaders(); - filePartheader.setContentType(APPLICATION_OCTET_STREAM); - val filePart = new HttpEntity(file, filePartheader); - - multiParts.add("info", infoPart); - multiParts.add("file", filePart); - return multiParts; - } + @PostMapping(path = "/multipart/upload6", consumes = MULTIPART_FORM_DATA_VALUE) + public ResponseEntity upload6(@RequestParam("popa1") MultipartFile popa1, + @RequestParam("popa2") MultipartFile popa2) + throws Exception { + HttpStatus status = I_AM_A_TEAPOT; + String result = ""; + if (popa1 != null && popa2 != null) { + status = OK; + result = new String(popa1.getBytes()) + new String(popa2.getBytes()); + } + return ResponseEntity.status(status).body(result); + } + + @GetMapping(path = "/multipart/download/{fileId}", produces = MULTIPART_FORM_DATA_VALUE) + public MultiValueMap download(@PathVariable("fileId") String fileId) { + val multiParts = new LinkedMultiValueMap(); + + val infoString = "The text for file ID " + fileId + ". Testing unicode €"; + val infoPartheader = new HttpHeaders(); + infoPartheader.setContentType(new MediaType("text", "plain", UTF_8)); + + val infoPart = new HttpEntity(infoString, infoPartheader); + + val file = new ClassPathResource("testfile.txt"); + val filePartheader = new HttpHeaders(); + filePartheader.setContentType(APPLICATION_OCTET_STREAM); + val filePart = new HttpEntity(file, filePartheader); + + multiParts.add("info", infoPart); + multiParts.add("file", filePart); + return multiParts; + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java index ca495418f..92267c0c4 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringFormEncoderTest.java @@ -1,109 +1,105 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.feign.spring; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import java.util.HashMap; import java.util.List; - import lombok.val; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; - import feign.Response; -@SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class, properties = {"server.port=8080", - "feign.hystrix.enabled=false", "logging.level.feign.form.feign.spring.Client=DEBUG"}) +@SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class, + properties = {"server.port=8080", + "feign.hystrix.enabled=false", "logging.level.feign.form.feign.spring.Client=DEBUG"}) class SpringFormEncoderTest { - @Autowired - private Client client; - - @Test - void upload1Test() throws Exception { - val folder = "test_folder"; - val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); - val message = "message test"; - - assertThat(client.upload1(folder, file, message)) - .isEqualTo(new String(file.getBytes()) + ':' + message + ':' + folder); - } - - @Test - void upload2Test() throws Exception { - val folder = "test_folder"; - val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); - val message = "message test"; - - assertThat(client.upload2(file, folder, message)) - .isEqualTo(new String(file.getBytes()) + ':' + message + ':' + folder); - } - - @Test - void uploadFileNameAndContentTypeTest() throws Exception { - val folder = "test_folder"; - val file = new MockMultipartFile("file", "hello.dat", "application/octet-stream", "test".getBytes(UTF_8)); - val message = "message test"; - - assertThat(client.upload3(file, folder, message)) - .isEqualTo(file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder); - } - - @Test - void upload4Test() throws Exception { - val map = new HashMap(); - map.put("one", 1); - map.put("two", 2); - - val userName = "popa"; - val id = "42"; - - assertThat(client.upload4(id, map, userName)).isEqualTo(userName + ':' + id + ':' + map.size()); - } - - @Test - void upload5Test() throws Exception { - val file = new MockMultipartFile("popa.txt", "Hello world".getBytes(UTF_8)); - val dto = new Dto("field 1 value", 42, file); - - assertThat(client.upload5(dto)).isNotNull().extracting(Response::status).isEqualTo(200); - } - - @Test - void upload6ArrayTest() throws Exception { - val file1 = new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)); - val file2 = new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8)); - - assertThat(client.upload6Array(new MultipartFile[]{file1, file2})).isEqualTo("Hello world"); - } - - @Test - void upload6CollectionTest() throws Exception { - List list = asList( - (MultipartFile) new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)), - (MultipartFile) new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8))); - - assertThat(client.upload6Collection(list)).isEqualTo("Hello world"); - } + @Autowired + private Client client; + + @Test + void upload1Test() throws Exception { + val folder = "test_folder"; + val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); + val message = "message test"; + + assertThat(client.upload1(folder, file, message)) + .isEqualTo(new String(file.getBytes()) + ':' + message + ':' + folder); + } + + @Test + void upload2Test() throws Exception { + val folder = "test_folder"; + val file = new MockMultipartFile("file", "test".getBytes(UTF_8)); + val message = "message test"; + + assertThat(client.upload2(file, folder, message)) + .isEqualTo(new String(file.getBytes()) + ':' + message + ':' + folder); + } + + @Test + void uploadFileNameAndContentTypeTest() throws Exception { + val folder = "test_folder"; + val file = new MockMultipartFile("file", "hello.dat", "application/octet-stream", + "test".getBytes(UTF_8)); + val message = "message test"; + + assertThat(client.upload3(file, folder, message)) + .isEqualTo(file.getOriginalFilename() + ':' + file.getContentType() + ':' + folder); + } + + @Test + void upload4Test() throws Exception { + val map = new HashMap(); + map.put("one", 1); + map.put("two", 2); + + val userName = "popa"; + val id = "42"; + + assertThat(client.upload4(id, map, userName)).isEqualTo(userName + ':' + id + ':' + map.size()); + } + + @Test + void upload5Test() throws Exception { + val file = new MockMultipartFile("popa.txt", "Hello world".getBytes(UTF_8)); + val dto = new Dto("field 1 value", 42, file); + + assertThat(client.upload5(dto)).isNotNull().extracting(Response::status).isEqualTo(200); + } + + @Test + void upload6ArrayTest() throws Exception { + val file1 = new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)); + val file2 = new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8)); + + assertThat(client.upload6Array(new MultipartFile[] {file1, file2})).isEqualTo("Hello world"); + } + + @Test + void upload6CollectionTest() throws Exception { + List list = asList( + (MultipartFile) new MockMultipartFile("popa1", "popa1", null, "Hello".getBytes(UTF_8)), + (MultipartFile) new MockMultipartFile("popa2", "popa2", null, " world".getBytes(UTF_8))); + + assertThat(client.upload6Collection(list)).isEqualTo("Hello world"); + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java index 9fa4e58b9..21591df27 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/SpringMultipartDecoderTest.java @@ -1,24 +1,20 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.feign.spring; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -26,29 +22,30 @@ import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; -@SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class, properties = {"server.port=8081", - "feign.hystrix.enabled=false"}) +@SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class, + properties = {"server.port=8081", + "feign.hystrix.enabled=false"}) class SpringMultipartDecoderTest { - @Autowired - private DownloadClient downloadClient; + @Autowired + private DownloadClient downloadClient; - @Test - void downloadTest() throws Exception { - MultipartFile[] downloads = downloadClient.download("123"); + @Test + void downloadTest() throws Exception { + MultipartFile[] downloads = downloadClient.download("123"); - assertThat(downloads.length).isEqualTo(2); + assertThat(downloads.length).isEqualTo(2); - assertThat(downloads[0].getName()).isEqualTo("info"); + assertThat(downloads[0].getName()).isEqualTo("info"); - MediaType infoContentType = MediaType.parseMediaType(downloads[0].getContentType()); - assertThat(MediaType.TEXT_PLAIN.includes(infoContentType)).isTrue(); - assertThat(infoContentType.getCharset()).isNotNull(); - assertThat(IOUtils.toString(downloads[0].getInputStream(), infoContentType.getCharset().name())) - .isEqualTo("The text for file ID 123. Testing unicode €"); + MediaType infoContentType = MediaType.parseMediaType(downloads[0].getContentType()); + assertThat(MediaType.TEXT_PLAIN.includes(infoContentType)).isTrue(); + assertThat(infoContentType.getCharset()).isNotNull(); + assertThat(IOUtils.toString(downloads[0].getInputStream(), infoContentType.getCharset().name())) + .isEqualTo("The text for file ID 123. Testing unicode €"); - assertThat(downloads[1].getOriginalFilename()).isEqualTo("testfile.txt"); - assertThat(downloads[1].getContentType()).isEqualTo(MediaType.APPLICATION_OCTET_STREAM_VALUE); - assertThat(downloads[1].getSize()).isEqualTo(14); - } + assertThat(downloads[1].getOriginalFilename()).isEqualTo("testfile.txt"); + assertThat(downloads[1].getContentType()).isEqualTo(MediaType.APPLICATION_OCTET_STREAM_VALUE); + assertThat(downloads[1].getSize()).isEqualTo(14); + } } diff --git a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java index c54370994..21444f608 100644 --- a/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java +++ b/feign-form-spring/src/test/java/feign/form/feign/spring/converter/SpringManyMultipartFilesReaderTest.java @@ -1,30 +1,25 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.feign.spring.converter; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; - import lombok.val; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; @@ -32,48 +27,51 @@ import org.springframework.http.HttpInputMessage; import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; - import feign.form.spring.converter.SpringManyMultipartFilesReader; class SpringManyMultipartFilesReaderTest { - private static final String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; + private static final String DUMMY_MULTIPART_BOUNDARY = "Boundary_4_574237629_1500021738802"; - @Test - void readMultipartFormDataTest() throws IOException { - val multipartFilesReader = new SpringManyMultipartFilesReader(4096); - val multipartFiles = multipartFilesReader.read(MultipartFile[].class, new ValidMultipartMessage()); + @Test + void readMultipartFormDataTest() throws IOException { + val multipartFilesReader = new SpringManyMultipartFilesReader(4096); + val multipartFiles = + multipartFilesReader.read(MultipartFile[].class, new ValidMultipartMessage()); - assertThat(multipartFiles.length).isEqualTo(2); + assertThat(multipartFiles.length).isEqualTo(2); - assertThat(multipartFiles[0].getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - assertThat(multipartFiles[0].getName()).isEqualTo("form-item-1"); - assertThat(multipartFiles[0].isEmpty()).isFalse(); + assertThat(multipartFiles[0].getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); + assertThat(multipartFiles[0].getName()).isEqualTo("form-item-1"); + assertThat(multipartFiles[0].isEmpty()).isFalse(); - assertThat(multipartFiles[1].getContentType()).isEqualTo(MediaType.TEXT_PLAIN_VALUE); - assertThat(multipartFiles[1].getOriginalFilename()).isEqualTo("form-item-2-file-1"); - assertThat(IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")).isEqualTo("Plain text"); - } + assertThat(multipartFiles[1].getContentType()).isEqualTo(MediaType.TEXT_PLAIN_VALUE); + assertThat(multipartFiles[1].getOriginalFilename()).isEqualTo("form-item-2-file-1"); + assertThat(IOUtils.toString(multipartFiles[1].getInputStream(), "US-ASCII")) + .isEqualTo("Plain text"); + } - static class ValidMultipartMessage implements HttpInputMessage { + static class ValidMultipartMessage implements HttpInputMessage { - @Override - public InputStream getBody() throws IOException { - val multipartBody = "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + "Content-Type: application/json\r\n" - + "Content-Disposition: form-data; name=\"form-item-1\"\r\n" + "\r\n" + "{\"id\":1}" + "\r\n" + "--" - + DUMMY_MULTIPART_BOUNDARY + "\r\n" + "content-type: text/plain\r\n" - + "content-disposition: Form-Data; Filename=\"form-item-2-file-1\"; Name=\"form-item-2\"\r\n" - + "\r\n" + "Plain text" + "\r\n" + "--" + DUMMY_MULTIPART_BOUNDARY + "--\r\n"; + @Override + public InputStream getBody() throws IOException { + val multipartBody = "--" + DUMMY_MULTIPART_BOUNDARY + "\r\n" + + "Content-Type: application/json\r\n" + + "Content-Disposition: form-data; name=\"form-item-1\"\r\n" + "\r\n" + "{\"id\":1}" + + "\r\n" + "--" + + DUMMY_MULTIPART_BOUNDARY + "\r\n" + "content-type: text/plain\r\n" + + "content-disposition: Form-Data; Filename=\"form-item-2-file-1\"; Name=\"form-item-2\"\r\n" + + "\r\n" + "Plain text" + "\r\n" + "--" + DUMMY_MULTIPART_BOUNDARY + "--\r\n"; - return new ByteArrayInputStream(multipartBody.getBytes("US-ASCII")); - } + return new ByteArrayInputStream(multipartBody.getBytes("US-ASCII")); + } - @Override - public HttpHeaders getHeaders() { - val httpHeaders = new HttpHeaders(); - httpHeaders.put(CONTENT_TYPE, - singletonList(MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY)); - return httpHeaders; - } - } + @Override + public HttpHeaders getHeaders() { + val httpHeaders = new HttpHeaders(); + httpHeaders.put(CONTENT_TYPE, + singletonList(MULTIPART_FORM_DATA_VALUE + "; boundary=" + DUMMY_MULTIPART_BOUNDARY)); + return httpHeaders; + } + } } diff --git a/feign-form/pom.xml b/feign-form/pom.xml index 8c693f259..373d6a098 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -1,36 +1,110 @@ - + 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. - 4.0.0 +--> + - feign-form + 4.0.0 - io.github.openfeign - parent - 13.5-SNAPSHOT + io.github.openfeign + parent + 13.5-SNAPSHOT + feign-form + Open Feign Forms Core + + ${project.basedir}/.. + + + + + org.projectlombok + lombok + 1.18.34 + provided + + + ${project.groupId} + feign-core + + + + io.github.openfeign + feign-jackson + test + + + + org.springframework.boot + spring-boot-starter-web + 2.7.18 + test + + + org.springframework.boot + spring-boot-starter-test + 2.7.18 + test + + + + io.undertow + undertow-core + 2.3.14.Final + test + + + io.appulse + utils-java + 1.18.0 + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + org.assertj + assertj-core + test + + + + org.mockito + mockito-core + 5.12.0 + test + + + org.mockito + mockito-junit-jupiter + 5.12.0 + test + + @@ -43,20 +117,7 @@ limitations under the License. feign.form.multipart - + - - - - org.projectlombok - lombok - 1.18.34 - provided - - - ${project.groupId} - feign-core - - diff --git a/feign-form/src/main/java/feign/form/ContentProcessor.java b/feign-form/src/main/java/feign/form/ContentProcessor.java index d3ffb1658..8fcfb325d 100644 --- a/feign-form/src/main/java/feign/form/ContentProcessor.java +++ b/feign-form/src/main/java/feign/form/ContentProcessor.java @@ -1,24 +1,20 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import java.nio.charset.Charset; import java.util.Map; - import feign.RequestTemplate; import feign.codec.EncodeException; @@ -32,35 +28,32 @@ */ public interface ContentProcessor { - /** - * A content type header name. - */ - String CONTENT_TYPE_HEADER = "Content-Type"; - - /** - * End line symbols. - */ - String CRLF = "\r\n"; - - /** - * Processes a request. - * - * @param template - * Feign's request template. - * @param charset - * request charset from 'Content-Type' header (UTF-8 by default). - * @param data - * reqeust data. - * - * @throws EncodeException - * in case of any encode exception - */ - void process(RequestTemplate template, Charset charset, Map data) throws EncodeException; - - /** - * Returns supported {@link ContentType} of this processor. - * - * @return supported content type enum value. - */ - ContentType getSupportedContentType(); + /** + * A content type header name. + */ + String CONTENT_TYPE_HEADER = "Content-Type"; + + /** + * End line symbols. + */ + String CRLF = "\r\n"; + + /** + * Processes a request. + * + * @param template Feign's request template. + * @param charset request charset from 'Content-Type' header (UTF-8 by default). + * @param data reqeust data. + * + * @throws EncodeException in case of any encode exception + */ + void process(RequestTemplate template, Charset charset, Map data) + throws EncodeException; + + /** + * Returns supported {@link ContentType} of this processor. + * + * @return supported content type enum value. + */ + ContentType getSupportedContentType(); } diff --git a/feign-form/src/main/java/feign/form/ContentType.java b/feign-form/src/main/java/feign/form/ContentType.java index c1a5875e2..52562b374 100644 --- a/feign-form/src/main/java/feign/form/ContentType.java +++ b/feign-form/src/main/java/feign/form/ContentType.java @@ -1,23 +1,19 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static lombok.AccessLevel.PRIVATE; - import lombok.Getter; import lombok.experimental.FieldDefaults; import lombok.val; @@ -31,45 +27,44 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public enum ContentType { - /** - * Unknown content type. - */ - UNDEFINED("undefined"), - /** - * Url encoded content type. - */ - URLENCODED("application/x-www-form-urlencoded"), - /** - * Multipart form data content type. - */ - MULTIPART("multipart/form-data"); + /** + * Unknown content type. + */ + UNDEFINED("undefined"), + /** + * Url encoded content type. + */ + URLENCODED("application/x-www-form-urlencoded"), + /** + * Multipart form data content type. + */ + MULTIPART("multipart/form-data"); - String header; + String header; - ContentType(String header) { - this.header = header; - } + ContentType(String header) { + this.header = header; + } - /** - * Parses string to content type. - * - * @param str - * string representation of content type. - * - * @return {@link ContentType} instance or {@link ContentType#UNDEFINED}, if - * there is no such content type. - */ - public static ContentType of(String str) { - if (str == null) { - return UNDEFINED; - } + /** + * Parses string to content type. + * + * @param str string representation of content type. + * + * @return {@link ContentType} instance or {@link ContentType#UNDEFINED}, if there is no such + * content type. + */ + public static ContentType of(String str) { + if (str == null) { + return UNDEFINED; + } - val trimmed = str.trim(); - for (val type : values()) { - if (trimmed.startsWith(type.getHeader())) { - return type; - } - } - return UNDEFINED; - } + val trimmed = str.trim(); + for (val type : values()) { + if (trimmed.startsWith(type.getHeader())) { + return type; + } + } + return UNDEFINED; + } } diff --git a/feign-form/src/main/java/feign/form/FormData.java b/feign-form/src/main/java/feign/form/FormData.java index 92d5a7f1e..6b2bebfef 100644 --- a/feign-form/src/main/java/feign/form/FormData.java +++ b/feign-form/src/main/java/feign/form/FormData.java @@ -1,23 +1,19 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static lombok.AccessLevel.PRIVATE; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -25,8 +21,8 @@ import lombok.experimental.FieldDefaults; /** - * This object encapsulates a byte array and its associated content type. Use if - * if you want to specify the content type of your provided byte array. + * This object encapsulates a byte array and its associated content type. Use if if you want to + * specify the content type of your provided byte array. * * @since 24.03.2018 * @author Guillaume Simard @@ -38,9 +34,9 @@ @FieldDefaults(level = PRIVATE) public class FormData { - String contentType; + String contentType; - String fileName; + String fileName; - byte[] data; + byte[] data; } diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index fb01421c6..c386fa585 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.form.util.PojoUtil.isUserPojo; @@ -21,17 +18,14 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static lombok.AccessLevel.PRIVATE; - import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; - import lombok.experimental.FieldDefaults; import lombok.val; - import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; @@ -44,97 +38,97 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class FormEncoder implements Encoder { - private static final String CONTENT_TYPE_HEADER; - - private static final Pattern CHARSET_PATTERN; - - static { - CONTENT_TYPE_HEADER = "Content-Type"; - CHARSET_PATTERN = Pattern.compile("(?<=charset=)([\\w\\-]+)"); - } - - Encoder delegate; - - Map processors; - - /** - * Constructor with the default Feign's encoder as a delegate. - */ - public FormEncoder() { - this(new Encoder.Default()); - } - - /** - * Constructor with specified delegate encoder. - * - * @param delegate - * delegate encoder, if this encoder couldn't encode object. - */ - public FormEncoder(Encoder delegate) { - this.delegate = delegate; - - val list = asList(new MultipartFormContentProcessor(delegate), new UrlencodedFormContentProcessor()); - - processors = new HashMap(list.size(), 1.F); - for (ContentProcessor processor : list) { - processors.put(processor.getSupportedContentType(), processor); - } - } - - @Override - @SuppressWarnings("unchecked") - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - String contentTypeValue = getContentTypeValue(template.headers()); - val contentType = ContentType.of(contentTypeValue); - if (processors.containsKey(contentType) == false) { - delegate.encode(object, bodyType, template); - return; - } - - Map data; - if (object instanceof Map) { - data = (Map) object; - } else if (isUserPojo(bodyType)) { - data = toMap(object); - } else { - delegate.encode(object, bodyType, template); - return; - } - - val charset = getCharset(contentTypeValue); - processors.get(contentType).process(template, charset, data); - } - - /** - * Returns {@link ContentProcessor} for specific {@link ContentType}. - * - * @param type - * a type for content processor search. - * - * @return {@link ContentProcessor} instance for specified type or null. - */ - public final ContentProcessor getContentProcessor(ContentType type) { - return processors.get(type); - } - - @SuppressWarnings("PMD.AvoidBranchingStatementAsLastInLoop") - private String getContentTypeValue(Map> headers) { - for (val entry : headers.entrySet()) { - if (!entry.getKey().equalsIgnoreCase(CONTENT_TYPE_HEADER)) { - continue; - } - for (val contentTypeValue : entry.getValue()) { - if (contentTypeValue == null) { - continue; - } - return contentTypeValue; - } - } - return null; - } - - private Charset getCharset(String contentTypeValue) { - val matcher = CHARSET_PATTERN.matcher(contentTypeValue); - return matcher.find() ? Charset.forName(matcher.group(1)) : UTF_8; - } + private static final String CONTENT_TYPE_HEADER; + + private static final Pattern CHARSET_PATTERN; + + static { + CONTENT_TYPE_HEADER = "Content-Type"; + CHARSET_PATTERN = Pattern.compile("(?<=charset=)([\\w\\-]+)"); + } + + Encoder delegate; + + Map processors; + + /** + * Constructor with the default Feign's encoder as a delegate. + */ + public FormEncoder() { + this(new Encoder.Default()); + } + + /** + * Constructor with specified delegate encoder. + * + * @param delegate delegate encoder, if this encoder couldn't encode object. + */ + public FormEncoder(Encoder delegate) { + this.delegate = delegate; + + val list = + asList(new MultipartFormContentProcessor(delegate), new UrlencodedFormContentProcessor()); + + processors = new HashMap(list.size(), 1.F); + for (ContentProcessor processor : list) { + processors.put(processor.getSupportedContentType(), processor); + } + } + + @Override + @SuppressWarnings("unchecked") + public void encode(Object object, Type bodyType, RequestTemplate template) + throws EncodeException { + String contentTypeValue = getContentTypeValue(template.headers()); + val contentType = ContentType.of(contentTypeValue); + if (processors.containsKey(contentType) == false) { + delegate.encode(object, bodyType, template); + return; + } + + Map data; + if (object instanceof Map) { + data = (Map) object; + } else if (isUserPojo(bodyType)) { + data = toMap(object); + } else { + delegate.encode(object, bodyType, template); + return; + } + + val charset = getCharset(contentTypeValue); + processors.get(contentType).process(template, charset, data); + } + + /** + * Returns {@link ContentProcessor} for specific {@link ContentType}. + * + * @param type a type for content processor search. + * + * @return {@link ContentProcessor} instance for specified type or null. + */ + public final ContentProcessor getContentProcessor(ContentType type) { + return processors.get(type); + } + + @SuppressWarnings("PMD.AvoidBranchingStatementAsLastInLoop") + private String getContentTypeValue(Map> headers) { + for (val entry : headers.entrySet()) { + if (!entry.getKey().equalsIgnoreCase(CONTENT_TYPE_HEADER)) { + continue; + } + for (val contentTypeValue : entry.getValue()) { + if (contentTypeValue == null) { + continue; + } + return contentTypeValue; + } + } + return null; + } + + private Charset getCharset(String contentTypeValue) { + val matcher = CHARSET_PATTERN.matcher(contentTypeValue); + return matcher.find() ? Charset.forName(matcher.group(1)) : UTF_8; + } } diff --git a/feign-form/src/main/java/feign/form/FormProperty.java b/feign-form/src/main/java/feign/form/FormProperty.java index af204e0c4..215fb86b2 100644 --- a/feign-form/src/main/java/feign/form/FormProperty.java +++ b/feign-form/src/main/java/feign/form/FormProperty.java @@ -1,24 +1,20 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -33,8 +29,8 @@ @Retention(RUNTIME) public @interface FormProperty { - /** - * The name of the property. - */ - String value(); + /** + * The name of the property. + */ + String value(); } diff --git a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java index cd79cbf95..2fbe96721 100644 --- a/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/MultipartFormContentProcessor.java @@ -1,24 +1,20 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.form.ContentType.MULTIPART; import static lombok.AccessLevel.PRIVATE; - import java.io.IOException; import java.nio.charset.Charset; import java.util.Collection; @@ -26,10 +22,8 @@ import java.util.Deque; import java.util.LinkedList; import java.util.Map; - import lombok.experimental.FieldDefaults; import lombok.val; - import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; @@ -52,110 +46,108 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class MultipartFormContentProcessor implements ContentProcessor { - Deque writers; - - Writer defaultPerocessor; - - /** - * Constructor with specific delegate encoder. - * - * @param delegate - * specific delegate encoder for cases, when this processor couldn't - * handle request parameter. - */ - public MultipartFormContentProcessor(Encoder delegate) { - writers = new LinkedList<>(); - addWriter(new ByteArrayWriter()); - addWriter(new FormDataWriter()); - addWriter(new SingleFileWriter()); - addWriter(new ManyFilesWriter()); - addWriter(new SingleParameterWriter()); - addWriter(new ManyParametersWriter()); - addWriter(new PojoWriter(writers)); - - defaultPerocessor = new DelegateWriter(delegate); - } - - @Override - public void process(RequestTemplate template, Charset charset, Map data) throws EncodeException { - val boundary = Long.toHexString(System.currentTimeMillis()); - try (val output = new Output(charset)) { - for (val entry : data.entrySet()) { - if (entry == null || entry.getKey() == null || entry.getValue() == null) { - continue; - } - val writer = findApplicableWriter(entry.getValue()); - writer.write(output, boundary, entry.getKey(), entry.getValue()); - } - - output.write("--").write(boundary).write("--").write(CRLF); - - val contentTypeHeaderValue = new StringBuilder().append(getSupportedContentType().getHeader()) - .append("; charset=").append(charset.name()).append("; boundary=").append(boundary).toString(); - - template.header(CONTENT_TYPE_HEADER, Collections.emptyList()); // reset header - template.header(CONTENT_TYPE_HEADER, contentTypeHeaderValue); - - // Feign's clients try to determine binary/string content by charset presence - // so, I set it to null (in spite of availability charset) for backward - // compatibility. - val bytes = output.toByteArray(); - template.body(bytes, null); - } catch (IOException ex) { - throw new EncodeException("Output closing error", ex); - } - } - - @Override - public ContentType getSupportedContentType() { - return MULTIPART; - } - - /** - * Adds {@link Writer} instance in runtime. - * - * @param writer - * additional writer. - */ - public final void addWriter(Writer writer) { - writers.add(writer); - } - - /** - * Adds {@link Writer} instance in runtime at the beginning of writers list. - * - * @param writer - * additional writer. - */ - public final void addFirstWriter(Writer writer) { - writers.addFirst(writer); - } - - /** - * Adds {@link Writer} instance in runtime at the end of writers list. - * - * @param writer - * additional writer. - */ - public final void addLastWriter(Writer writer) { - writers.addLast(writer); - } - - /** - * Returns the unmodifiable collection of all writers. - * - * @return writers collection. - */ - public final Collection getWriters() { - return Collections.unmodifiableCollection(writers); - } - - private Writer findApplicableWriter(Object value) { - for (val writer : writers) { - if (writer.isApplicable(value)) { - return writer; - } - } - return defaultPerocessor; - } + Deque writers; + + Writer defaultPerocessor; + + /** + * Constructor with specific delegate encoder. + * + * @param delegate specific delegate encoder for cases, when this processor couldn't handle + * request parameter. + */ + public MultipartFormContentProcessor(Encoder delegate) { + writers = new LinkedList<>(); + addWriter(new ByteArrayWriter()); + addWriter(new FormDataWriter()); + addWriter(new SingleFileWriter()); + addWriter(new ManyFilesWriter()); + addWriter(new SingleParameterWriter()); + addWriter(new ManyParametersWriter()); + addWriter(new PojoWriter(writers)); + + defaultPerocessor = new DelegateWriter(delegate); + } + + @Override + public void process(RequestTemplate template, Charset charset, Map data) + throws EncodeException { + val boundary = Long.toHexString(System.currentTimeMillis()); + try (val output = new Output(charset)) { + for (val entry : data.entrySet()) { + if (entry == null || entry.getKey() == null || entry.getValue() == null) { + continue; + } + val writer = findApplicableWriter(entry.getValue()); + writer.write(output, boundary, entry.getKey(), entry.getValue()); + } + + output.write("--").write(boundary).write("--").write(CRLF); + + val contentTypeHeaderValue = new StringBuilder().append(getSupportedContentType().getHeader()) + .append("; charset=").append(charset.name()).append("; boundary=").append(boundary) + .toString(); + + template.header(CONTENT_TYPE_HEADER, Collections.emptyList()); // reset header + template.header(CONTENT_TYPE_HEADER, contentTypeHeaderValue); + + // Feign's clients try to determine binary/string content by charset presence + // so, I set it to null (in spite of availability charset) for backward + // compatibility. + val bytes = output.toByteArray(); + template.body(bytes, null); + } catch (IOException ex) { + throw new EncodeException("Output closing error", ex); + } + } + + @Override + public ContentType getSupportedContentType() { + return MULTIPART; + } + + /** + * Adds {@link Writer} instance in runtime. + * + * @param writer additional writer. + */ + public final void addWriter(Writer writer) { + writers.add(writer); + } + + /** + * Adds {@link Writer} instance in runtime at the beginning of writers list. + * + * @param writer additional writer. + */ + public final void addFirstWriter(Writer writer) { + writers.addFirst(writer); + } + + /** + * Adds {@link Writer} instance in runtime at the end of writers list. + * + * @param writer additional writer. + */ + public final void addLastWriter(Writer writer) { + writers.addLast(writer); + } + + /** + * Returns the unmodifiable collection of all writers. + * + * @return writers collection. + */ + public final Collection getWriters() { + return Collections.unmodifiableCollection(writers); + } + + private Writer findApplicableWriter(Object value) { + for (val writer : writers) { + if (writer.isApplicable(value)) { + return writer; + } + } + return defaultPerocessor; + } } diff --git a/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java index 69d24915d..2c1e1d1dd 100644 --- a/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java +++ b/feign-form/src/main/java/feign/form/UrlencodedFormContentProcessor.java @@ -1,33 +1,27 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.form.ContentType.URLENCODED; - import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; - import lombok.SneakyThrows; import lombok.val; - import feign.RequestTemplate; import feign.codec.EncodeException; @@ -38,79 +32,82 @@ */ public class UrlencodedFormContentProcessor implements ContentProcessor { - private static final char QUERY_DELIMITER = '&'; - - private static final char EQUAL_SIGN = '='; - - @SneakyThrows - private static String encode(Object string, Charset charset) { - return URLEncoder.encode(string.toString(), charset.name()); - } - - @Override - public void process(RequestTemplate template, Charset charset, Map data) throws EncodeException { - val bodyData = new StringBuilder(); - for (Entry entry : data.entrySet()) { - if (entry == null || entry.getKey() == null) { - continue; - } - if (bodyData.length() > 0) { - bodyData.append(QUERY_DELIMITER); - } - bodyData.append(createKeyValuePair(entry, charset)); - } - - val contentTypeValue = new StringBuilder().append(getSupportedContentType().getHeader()).append("; charset=") - .append(charset.name()).toString(); - - val bytes = bodyData.toString().getBytes(charset); - - template.header(CONTENT_TYPE_HEADER, Collections.emptyList()); // reset header - template.header(CONTENT_TYPE_HEADER, contentTypeValue); - template.body(bytes, charset); - } - - @Override - public ContentType getSupportedContentType() { - return URLENCODED; - } - - private String createKeyValuePair(Entry entry, Charset charset) { - String encodedKey = encode(entry.getKey(), charset); - Object value = entry.getValue(); - - if (value == null) { - return encodedKey; - } else if (value.getClass().isArray()) { - return createKeyValuePairFromArray(encodedKey, value, charset); - } else if (value instanceof Collection) { - return createKeyValuePairFromCollection(encodedKey, value, charset); - } - return new StringBuilder().append(encodedKey).append(EQUAL_SIGN).append(encode(value, charset)).toString(); - } - - private String createKeyValuePairFromCollection(String key, Object values, Charset charset) { - val collection = (Collection) values; - val array = collection.toArray(new Object[0]); - return createKeyValuePairFromArray(key, array, charset); - } - - private String createKeyValuePairFromArray(String key, Object values, Charset charset) { - val result = new StringBuilder(); - val array = (Object[]) values; - - for (int index = 0; index < array.length; index++) { - val value = array[index]; - if (value == null) { - continue; - } - - if (index > 0) { - result.append(QUERY_DELIMITER); - } - - result.append(key).append(EQUAL_SIGN).append(encode(value, charset)); - } - return result.toString(); - } + private static final char QUERY_DELIMITER = '&'; + + private static final char EQUAL_SIGN = '='; + + @SneakyThrows + private static String encode(Object string, Charset charset) { + return URLEncoder.encode(string.toString(), charset.name()); + } + + @Override + public void process(RequestTemplate template, Charset charset, Map data) + throws EncodeException { + val bodyData = new StringBuilder(); + for (Entry entry : data.entrySet()) { + if (entry == null || entry.getKey() == null) { + continue; + } + if (bodyData.length() > 0) { + bodyData.append(QUERY_DELIMITER); + } + bodyData.append(createKeyValuePair(entry, charset)); + } + + val contentTypeValue = + new StringBuilder().append(getSupportedContentType().getHeader()).append("; charset=") + .append(charset.name()).toString(); + + val bytes = bodyData.toString().getBytes(charset); + + template.header(CONTENT_TYPE_HEADER, Collections.emptyList()); // reset header + template.header(CONTENT_TYPE_HEADER, contentTypeValue); + template.body(bytes, charset); + } + + @Override + public ContentType getSupportedContentType() { + return URLENCODED; + } + + private String createKeyValuePair(Entry entry, Charset charset) { + String encodedKey = encode(entry.getKey(), charset); + Object value = entry.getValue(); + + if (value == null) { + return encodedKey; + } else if (value.getClass().isArray()) { + return createKeyValuePairFromArray(encodedKey, value, charset); + } else if (value instanceof Collection) { + return createKeyValuePairFromCollection(encodedKey, value, charset); + } + return new StringBuilder().append(encodedKey).append(EQUAL_SIGN).append(encode(value, charset)) + .toString(); + } + + private String createKeyValuePairFromCollection(String key, Object values, Charset charset) { + val collection = (Collection) values; + val array = collection.toArray(new Object[0]); + return createKeyValuePairFromArray(key, array, charset); + } + + private String createKeyValuePairFromArray(String key, Object values, Charset charset) { + val result = new StringBuilder(); + val array = (Object[]) values; + + for (int index = 0; index < array.length; index++) { + val value = array[index]; + if (value == null) { + continue; + } + + if (index > 0) { + result.append(QUERY_DELIMITER); + } + + result.append(key).append(EQUAL_SIGN).append(encode(value, charset)); + } + return result.toString(); + } } diff --git a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java index 4df095860..2dbe3420d 100644 --- a/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/AbstractWriter.java @@ -1,27 +1,21 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import static feign.form.ContentProcessor.CRLF; - import java.net.URLConnection; - import lombok.val; - import feign.codec.EncodeException; /** @@ -31,64 +25,61 @@ */ public abstract class AbstractWriter implements Writer { - @Override - public void write(Output output, String boundary, String key, Object value) throws EncodeException { - output.write("--").write(boundary).write(CRLF); - write(output, key, value); - output.write(CRLF); - } + @Override + public void write(Output output, String boundary, String key, Object value) + throws EncodeException { + output.write("--").write(boundary).write(CRLF); + write(output, key, value); + output.write(CRLF); + } - /** - * Writes data for it's children. - * - * @param output - * output writer. - * @param key - * name for piece of data. - * @param value - * piece of data. - * - * @throws EncodeException - * in case of write errors - */ - @SuppressWarnings({"PMD.UncommentedEmptyMethodBody", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) - protected void write(Output output, String key, Object value) throws EncodeException { - } + /** + * Writes data for it's children. + * + * @param output output writer. + * @param key name for piece of data. + * @param value piece of data. + * + * @throws EncodeException in case of write errors + */ + @SuppressWarnings({"PMD.UncommentedEmptyMethodBody", + "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) + protected void write(Output output, String key, Object value) throws EncodeException {} - /** - * Writes file's metadata. - * - * @param output - * output writer. - * @param name - * name for piece of data. - * @param fileName - * file name. - * @param contentType - * type of file content. May be the {@code null}, in that case it - * will be determined by file name. - */ - protected void writeFileMetadata(Output output, String name, String fileName, String contentType) { - val contentDespositionBuilder = new StringBuilder().append("Content-Disposition: form-data; name=\"") - .append(name).append("\""); - if (fileName != null) { - contentDespositionBuilder.append("; ").append("filename=\"").append(fileName).append("\""); - } + /** + * Writes file's metadata. + * + * @param output output writer. + * @param name name for piece of data. + * @param fileName file name. + * @param contentType type of file content. May be the {@code null}, in that case it will be + * determined by file name. + */ + protected void writeFileMetadata(Output output, + String name, + String fileName, + String contentType) { + val contentDespositionBuilder = + new StringBuilder().append("Content-Disposition: form-data; name=\"") + .append(name).append("\""); + if (fileName != null) { + contentDespositionBuilder.append("; ").append("filename=\"").append(fileName).append("\""); + } - String fileContentType = contentType; - if (fileContentType == null) { - if (fileName != null) { - fileContentType = URLConnection.guessContentTypeFromName(fileName); - } - if (fileContentType == null) { - fileContentType = "application/octet-stream"; - } - } + String fileContentType = contentType; + if (fileContentType == null) { + if (fileName != null) { + fileContentType = URLConnection.guessContentTypeFromName(fileName); + } + if (fileContentType == null) { + fileContentType = "application/octet-stream"; + } + } - val string = new StringBuilder().append(contentDespositionBuilder.toString()).append(CRLF) - .append("Content-Type: ").append(fileContentType).append(CRLF) - .append("Content-Transfer-Encoding: binary").append(CRLF).append(CRLF).toString(); + val string = new StringBuilder().append(contentDespositionBuilder.toString()).append(CRLF) + .append("Content-Type: ").append(fileContentType).append(CRLF) + .append("Content-Transfer-Encoding: binary").append(CRLF).append(CRLF).toString(); - output.write(string); - } + output.write(string); + } } diff --git a/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java index 28fbd5998..78c66e96d 100644 --- a/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ByteArrayWriter.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import feign.codec.EncodeException; @@ -25,16 +22,16 @@ */ public class ByteArrayWriter extends AbstractWriter { - @Override - public boolean isApplicable(Object value) { - return value instanceof byte[]; - } + @Override + public boolean isApplicable(Object value) { + return value instanceof byte[]; + } - @Override - protected void write(Output output, String key, Object value) throws EncodeException { - writeFileMetadata(output, key, null, null); + @Override + protected void write(Output output, String key, Object value) throws EncodeException { + writeFileMetadata(output, key, null, null); - byte[] bytes = (byte[]) value; - output.write(bytes); - } + byte[] bytes = (byte[]) value; + output.write(bytes); + } } diff --git a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java index e39236880..fd2dd6f73 100644 --- a/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/DelegateWriter.java @@ -1,27 +1,22 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import static lombok.AccessLevel.PRIVATE; - import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.val; - import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; @@ -35,21 +30,21 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class DelegateWriter extends AbstractWriter { - Encoder delegate; + Encoder delegate; - SingleParameterWriter parameterWriter = new SingleParameterWriter(); + SingleParameterWriter parameterWriter = new SingleParameterWriter(); - @Override - public boolean isApplicable(Object value) { - return true; - } + @Override + public boolean isApplicable(Object value) { + return true; + } - @Override - protected void write(Output output, String key, Object value) throws EncodeException { - val fake = new RequestTemplate(); - delegate.encode(value, value.getClass(), fake); - val bytes = fake.body(); - val string = new String(bytes, output.getCharset()).replaceAll("\n", ""); - parameterWriter.write(output, key, string); - } + @Override + protected void write(Output output, String key, Object value) throws EncodeException { + val fake = new RequestTemplate(); + delegate.encode(value, value.getClass(), fake); + val bytes = fake.body(); + val string = new String(bytes, output.getCharset()).replaceAll("\n", ""); + parameterWriter.write(output, key, string); + } } diff --git a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java index fbb288616..160341bfc 100644 --- a/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/FormDataWriter.java @@ -1,23 +1,19 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import lombok.val; - import feign.codec.EncodeException; import feign.form.FormData; @@ -29,15 +25,15 @@ */ public class FormDataWriter extends AbstractWriter { - @Override - public boolean isApplicable(Object value) { - return value instanceof FormData; - } + @Override + public boolean isApplicable(Object value) { + return value instanceof FormData; + } - @Override - protected void write(Output output, String key, Object value) throws EncodeException { - val formData = (FormData) value; - writeFileMetadata(output, key, formData.getFileName(), formData.getContentType()); - output.write(formData.getData()); - } + @Override + protected void write(Output output, String key, Object value) throws EncodeException { + val formData = (FormData) value; + writeFileMetadata(output, key, formData.getFileName(), formData.getContentType()); + output.write(formData.getData()); + } } diff --git a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java index c732f34f0..dac715aac 100644 --- a/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ManyFilesWriter.java @@ -1,28 +1,22 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import static lombok.AccessLevel.PRIVATE; - import java.io.File; - import lombok.experimental.FieldDefaults; import lombok.val; - import feign.codec.EncodeException; /** @@ -33,35 +27,36 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class ManyFilesWriter extends AbstractWriter { - SingleFileWriter fileWriter = new SingleFileWriter(); - - @Override - public boolean isApplicable(Object value) { - if (value instanceof File[]) { - return true; - } - if (!(value instanceof Iterable)) { - return false; - } - val iterable = (Iterable) value; - val iterator = iterable.iterator(); - return iterator.hasNext() && iterator.next() instanceof File; - } - - @Override - public void write(Output output, String boundary, String key, Object value) throws EncodeException { - if (value instanceof File[]) { - val files = (File[]) value; - for (val file : files) { - fileWriter.write(output, boundary, key, file); - } - } else if (value instanceof Iterable) { - val iterable = (Iterable) value; - for (val file : iterable) { - fileWriter.write(output, boundary, key, file); - } - } else { - throw new IllegalArgumentException(); - } - } + SingleFileWriter fileWriter = new SingleFileWriter(); + + @Override + public boolean isApplicable(Object value) { + if (value instanceof File[]) { + return true; + } + if (!(value instanceof Iterable)) { + return false; + } + val iterable = (Iterable) value; + val iterator = iterable.iterator(); + return iterator.hasNext() && iterator.next() instanceof File; + } + + @Override + public void write(Output output, String boundary, String key, Object value) + throws EncodeException { + if (value instanceof File[]) { + val files = (File[]) value; + for (val file : files) { + fileWriter.write(output, boundary, key, file); + } + } else if (value instanceof Iterable) { + val iterable = (Iterable) value; + for (val file : iterable) { + fileWriter.write(output, boundary, key, file); + } + } else { + throw new IllegalArgumentException(); + } + } } diff --git a/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java b/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java index e8e7a20c1..463014dab 100644 --- a/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/ManyParametersWriter.java @@ -1,26 +1,21 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import static lombok.AccessLevel.PRIVATE; - import lombok.experimental.FieldDefaults; import lombok.val; - import feign.codec.EncodeException; /** @@ -31,34 +26,35 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class ManyParametersWriter extends AbstractWriter { - SingleParameterWriter parameterWriter = new SingleParameterWriter(); - - @Override - public boolean isApplicable(Object value) { - if (value.getClass().isArray()) { - Object[] values = (Object[]) value; - return values.length > 0 && parameterWriter.isApplicable(values[0]); - } - if (!(value instanceof Iterable)) { - return false; - } - val iterable = (Iterable) value; - val iterator = iterable.iterator(); - return iterator.hasNext() && parameterWriter.isApplicable(iterator.next()); - } - - @Override - public void write(Output output, String boundary, String key, Object value) throws EncodeException { - if (value.getClass().isArray()) { - val objects = (Object[]) value; - for (val object : objects) { - parameterWriter.write(output, boundary, key, object); - } - } else if (value instanceof Iterable) { - val iterable = (Iterable) value; - for (val object : iterable) { - parameterWriter.write(output, boundary, key, object); - } - } - } + SingleParameterWriter parameterWriter = new SingleParameterWriter(); + + @Override + public boolean isApplicable(Object value) { + if (value.getClass().isArray()) { + Object[] values = (Object[]) value; + return values.length > 0 && parameterWriter.isApplicable(values[0]); + } + if (!(value instanceof Iterable)) { + return false; + } + val iterable = (Iterable) value; + val iterator = iterable.iterator(); + return iterator.hasNext() && parameterWriter.isApplicable(iterator.next()); + } + + @Override + public void write(Output output, String boundary, String key, Object value) + throws EncodeException { + if (value.getClass().isArray()) { + val objects = (Object[]) value; + for (val object : objects) { + parameterWriter.write(output, boundary, key, object); + } + } else if (value instanceof Iterable) { + val iterable = (Iterable) value; + for (val object : iterable) { + parameterWriter.write(output, boundary, key, object); + } + } + } } diff --git a/feign-form/src/main/java/feign/form/multipart/Output.java b/feign-form/src/main/java/feign/form/multipart/Output.java index cc0284de7..b80294970 100644 --- a/feign-form/src/main/java/feign/form/multipart/Output.java +++ b/feign-form/src/main/java/feign/form/multipart/Output.java @@ -1,28 +1,23 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import static lombok.AccessLevel.PRIVATE; - import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.nio.charset.Charset; - import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -37,66 +32,61 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class Output implements Closeable { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - @Getter - Charset charset; + @Getter + Charset charset; - /** - * Writes the string to the output. - * - * @param string - * string to write to this output - * - * @return this output - */ - public Output write(String string) { - return write(string.getBytes(charset)); - } + /** + * Writes the string to the output. + * + * @param string string to write to this output + * + * @return this output + */ + public Output write(String string) { + return write(string.getBytes(charset)); + } - /** - * Writes the byte array to the output. - * - * @param bytes - * byte arrays to write to this output - * - * @return this output - */ - @SneakyThrows - public Output write(byte[] bytes) { - outputStream.write(bytes); - return this; - } + /** + * Writes the byte array to the output. + * + * @param bytes byte arrays to write to this output + * + * @return this output + */ + @SneakyThrows + public Output write(byte[] bytes) { + outputStream.write(bytes); + return this; + } - /** - * Writes the byte array to the output with specified offset and fixed length. - * - * @param bytes - * byte arrays to write to this output - * @param offset - * the offset within the array of the first byte to be read. Must be - * non-negative and no larger than bytes.length - * @param length - * the number of bytes to be read from the given array - * - * @return this output - */ - public Output write(byte[] bytes, int offset, int length) { - outputStream.write(bytes, offset, length); - return this; - } + /** + * Writes the byte array to the output with specified offset and fixed length. + * + * @param bytes byte arrays to write to this output + * @param offset the offset within the array of the first byte to be read. Must be non-negative + * and no larger than bytes.length + * @param length the number of bytes to be read from the given array + * + * @return this output + */ + public Output write(byte[] bytes, int offset, int length) { + outputStream.write(bytes, offset, length); + return this; + } - /** - * Returns byte array representation of this output class. - * - * @return byte array representation of output - */ - public byte[] toByteArray() { - return outputStream.toByteArray(); - } + /** + * Returns byte array representation of this output class. + * + * @return byte array representation of output + */ + public byte[] toByteArray() { + return outputStream.toByteArray(); + } - @Override - public void close() throws IOException { - outputStream.close(); - } + @Override + public void close() throws IOException { + outputStream.close(); + } } diff --git a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java index 9088f9637..6a9f60a34 100644 --- a/feign-form/src/main/java/feign/form/multipart/PojoWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/PojoWriter.java @@ -1,29 +1,24 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import static feign.form.util.PojoUtil.isUserPojo; import static feign.form.util.PojoUtil.toMap; import static lombok.AccessLevel.PRIVATE; - import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.val; - import feign.codec.EncodeException; /** @@ -35,32 +30,33 @@ @FieldDefaults(level = PRIVATE, makeFinal = true) public class PojoWriter extends AbstractWriter { - Iterable writers; - - @Override - public boolean isApplicable(Object object) { - return isUserPojo(object); - } - - @Override - public void write(Output output, String boundary, String key, Object object) throws EncodeException { - val map = toMap(object); - for (val entry : map.entrySet()) { - val writer = findApplicableWriter(entry.getValue()); - if (writer == null) { - continue; - } - - writer.write(output, boundary, entry.getKey(), entry.getValue()); - } - } - - private Writer findApplicableWriter(Object value) { - for (val writer : writers) { - if (writer.isApplicable(value)) { - return writer; - } - } - return null; - } + Iterable writers; + + @Override + public boolean isApplicable(Object object) { + return isUserPojo(object); + } + + @Override + public void write(Output output, String boundary, String key, Object object) + throws EncodeException { + val map = toMap(object); + for (val entry : map.entrySet()) { + val writer = findApplicableWriter(entry.getValue()); + if (writer == null) { + continue; + } + + writer.write(output, boundary, entry.getKey(), entry.getValue()); + } + } + + private Writer findApplicableWriter(Object value) { + for (val writer : writers) { + if (writer.isApplicable(value)) { + return writer; + } + } + return null; + } } diff --git a/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java index e7443d700..140c8e223 100644 --- a/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/SingleFileWriter.java @@ -1,28 +1,23 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; - import lombok.val; - import feign.codec.EncodeException; /** @@ -32,26 +27,26 @@ */ public class SingleFileWriter extends AbstractWriter { - @Override - public boolean isApplicable(Object value) { - return value instanceof File; - } - - @Override - protected void write(Output output, String key, Object value) throws EncodeException { - val file = (File) value; - writeFileMetadata(output, key, file.getName(), null); - - try (InputStream input = new FileInputStream(file)) { - val buf = new byte[4096]; - int length = input.read(buf); - while (length > 0) { - output.write(buf, 0, length); - length = input.read(buf); - } - } catch (IOException ex) { - val message = String.format("Writing file's '%s' content error", file.getName()); - throw new EncodeException(message, ex); - } - } + @Override + public boolean isApplicable(Object value) { + return value instanceof File; + } + + @Override + protected void write(Output output, String key, Object value) throws EncodeException { + val file = (File) value; + writeFileMetadata(output, key, file.getName(), null); + + try (InputStream input = new FileInputStream(file)) { + val buf = new byte[4096]; + int length = input.read(buf); + while (length > 0) { + output.write(buf, 0, length); + length = input.read(buf); + } + } catch (IOException ex) { + val message = String.format("Writing file's '%s' content error", file.getName()); + throw new EncodeException(message, ex); + } + } } diff --git a/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java b/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java index 1f8ffaadb..74818960d 100644 --- a/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java +++ b/feign-form/src/main/java/feign/form/multipart/SingleParameterWriter.java @@ -1,25 +1,20 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import static feign.form.ContentProcessor.CRLF; - import lombok.val; - import feign.codec.EncodeException; /** @@ -29,17 +24,19 @@ */ public class SingleParameterWriter extends AbstractWriter { - @Override - public boolean isApplicable(Object value) { - return value instanceof Number || value instanceof CharSequence || value instanceof Boolean; - } - - @Override - protected void write(Output output, String key, Object value) throws EncodeException { - val string = new StringBuilder().append("Content-Disposition: form-data; name=\"").append(key).append('"') - .append(CRLF).append("Content-Type: text/plain; charset=").append(output.getCharset().name()) - .append(CRLF).append(CRLF).append(value.toString()).toString(); - - output.write(string); - } + @Override + public boolean isApplicable(Object value) { + return value instanceof Number || value instanceof CharSequence || value instanceof Boolean; + } + + @Override + protected void write(Output output, String key, Object value) throws EncodeException { + val string = new StringBuilder().append("Content-Disposition: form-data; name=\"").append(key) + .append('"') + .append(CRLF).append("Content-Type: text/plain; charset=") + .append(output.getCharset().name()) + .append(CRLF).append(CRLF).append(value.toString()).toString(); + + output.write(string); + } } diff --git a/feign-form/src/main/java/feign/form/multipart/Writer.java b/feign-form/src/main/java/feign/form/multipart/Writer.java index 7c0e0d2b7..5b6f98243 100644 --- a/feign-form/src/main/java/feign/form/multipart/Writer.java +++ b/feign-form/src/main/java/feign/form/multipart/Writer.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.multipart; import feign.codec.EncodeException; @@ -25,30 +22,24 @@ */ public interface Writer { - /** - * Processing form data to request body. - * - * @param output - * output writer. - * @param boundary - * data boundary. - * @param key - * name for piece of data. - * @param value - * piece of data. - * - * @throws EncodeException - * in case of any encode exception - */ - void write(Output output, String boundary, String key, Object value) throws EncodeException; + /** + * Processing form data to request body. + * + * @param output output writer. + * @param boundary data boundary. + * @param key name for piece of data. + * @param value piece of data. + * + * @throws EncodeException in case of any encode exception + */ + void write(Output output, String boundary, String key, Object value) throws EncodeException; - /** - * Answers on question - "could this writer properly write the value". - * - * @param value - * object to write. - * - * @return {@code true} - if could write this object, otherwise {@code true} - */ - boolean isApplicable(Object value); + /** + * Answers on question - "could this writer properly write the value". + * + * @param value object to write. + * + * @return {@code true} - if could write this object, otherwise {@code true} + */ + boolean isApplicable(Object value); } diff --git a/feign-form/src/main/java/feign/form/util/PojoUtil.java b/feign-form/src/main/java/feign/form/util/PojoUtil.java index 5f152d3fe..cd352c330 100644 --- a/feign-form/src/main/java/feign/form/util/PojoUtil.java +++ b/feign-form/src/main/java/feign/form/util/PojoUtil.java @@ -1,25 +1,21 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.util; import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isStatic; import static lombok.AccessLevel.PRIVATE; - import feign.form.FormProperty; import java.lang.reflect.Field; import java.lang.reflect.Type; @@ -27,7 +23,6 @@ import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; -import javax.annotation.Nullable; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; @@ -42,59 +37,57 @@ */ public final class PojoUtil { - public static boolean isUserPojo(@NonNull Object object) { - val type = object.getClass(); - val packageName = type.getPackage().getName(); - return !packageName.startsWith("java."); - } + public static boolean isUserPojo(@NonNull Object object) { + val type = object.getClass(); + val packageName = type.getPackage().getName(); + return !packageName.startsWith("java."); + } - public static boolean isUserPojo(@NonNull Type type) { - val typeName = type.toString(); - return !typeName.startsWith("class java."); - } + public static boolean isUserPojo(@NonNull Type type) { + val typeName = type.toString(); + return !typeName.startsWith("class java."); + } - @SneakyThrows - @SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") - public static Map toMap(@NonNull Object object) { - val result = new HashMap(); - val type = object.getClass(); - for (val field : type.getDeclaredFields()) { - val modifiers = field.getModifiers(); - if (isFinal(modifiers) || isStatic(modifiers)) { - continue; - } - field.setAccessible(true); + @SneakyThrows + public static Map toMap(@NonNull Object object) { + val result = new HashMap(); + val type = object.getClass(); + for (val field : type.getDeclaredFields()) { + val modifiers = field.getModifiers(); + if (isFinal(modifiers) || isStatic(modifiers)) { + continue; + } + field.setAccessible(true); - val fieldValue = field.get(object); - if (fieldValue == null) { - continue; - } + val fieldValue = field.get(object); + if (fieldValue == null) { + continue; + } - val propertyKey = field.isAnnotationPresent(FormProperty.class) - ? field.getAnnotation(FormProperty.class).value() - : field.getName(); + val propertyKey = field.isAnnotationPresent(FormProperty.class) + ? field.getAnnotation(FormProperty.class).value() + : field.getName(); - result.put(propertyKey, fieldValue); - } - return result; - } + result.put(propertyKey, fieldValue); + } + return result; + } - private PojoUtil() throws UnexpectedException { - throw new UnexpectedException("It is not allowed to instantiate this class"); - } + private PojoUtil() throws UnexpectedException { + throw new UnexpectedException("It is not allowed to instantiate this class"); + } - @Setter - @NoArgsConstructor - @FieldDefaults(level = PRIVATE) - private static final class SetAccessibleAction implements PrivilegedAction { + @Setter + @NoArgsConstructor + @FieldDefaults(level = PRIVATE) + private static final class SetAccessibleAction implements PrivilegedAction { - @Nullable - Field field; + Field field; - @Override - public Object run() { - field.setAccessible(true); - return null; - } - } + @Override + public Object run() { + field.setAccessible(true); + return null; + } + } } diff --git a/feign-form/src/main/java/lombok.config b/feign-form/src/main/java/lombok.config deleted file mode 100644 index 26f5d95a3..000000000 --- a/feign-form/src/main/java/lombok.config +++ /dev/null @@ -1 +0,0 @@ -lombok.extern.findbugs.addSuppressFBWarnings=true diff --git a/feign-form/src/test/java/feign/form/BasicClientTest.java b/feign-form/src/test/java/feign/form/BasicClientTest.java index d4415cda6..a3a857ed1 100644 --- a/feign-form/src/test/java/feign/form/BasicClientTest.java +++ b/feign-form/src/test/java/feign/form/BasicClientTest.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.Logger.Level.FULL; @@ -21,17 +18,14 @@ import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.Map; - import lombok.val; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; - import feign.Feign; import feign.Logger.JavaLogger; import feign.Response; @@ -40,114 +34,124 @@ @SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class) class BasicClientTest { - private static final TestClient API; - - static { - API = Feign.builder().encoder(new FormEncoder(new JacksonEncoder())) - .logger(new JavaLogger(BasicClientTest.class).appendToFile("log.txt")).logLevel(FULL) - .target(TestClient.class, "http://localhost:8080"); - } - - @Test - void testForm() { - assertThat(API.form("1", "1")).isNotNull().extracting(Response::status).isEqualTo(200); - } - - @Test - void testFormException() { - assertThat(API.form("1", "2")).isNotNull().extracting(Response::status).isEqualTo(400); - } - - @Test - void testUpload() throws Exception { - val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - assertThat(path).exists(); - - assertThat(API.upload(path.toFile())).asLong().isEqualTo(Files.size(path)); - } - - @Test - void testUploadWithParam() throws Exception { - val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - assertThat(path).exists(); - - assertThat(API.upload(10, Boolean.TRUE, path.toFile())).asLong().isEqualTo(Files.size(path)); - } - - @Test - void testJson() { - val dto = new Dto("Artem", 11); - - assertThat(API.json(dto)).isEqualTo("ok"); - } - - @Test - void testQueryMap() { - Map value = singletonMap("filter", (Object) asList("one", "two", "three", "four")); - - assertThat(API.queryMap(value)).isEqualTo("4"); - } - - @Test - void testMultipleFilesArray() throws Exception { - val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - assertThat(path1).exists(); - - val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); - assertThat(path2).exists(); - - assertThat(API.uploadWithArray(new File[]{path1.toFile(), path2.toFile()})).asLong() - .isEqualTo(Files.size(path1) + Files.size(path2)); - } - - @Test - void testMultipleFilesList() throws Exception { - val path1 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - assertThat(path1).exists(); - - val path2 = Paths.get(Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); - assertThat(path2).exists(); - - assertThat(API.uploadWithList(asList(path1.toFile(), path2.toFile()))).asLong() - .isEqualTo(Files.size(path1) + Files.size(path2)); - } - - @Test - void testUploadWithDto() throws Exception { - val dto = new Dto("Artem", 11); - - val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); - assertThat(path).exists(); - - assertThat(API.uploadWithDto(dto, path.toFile())).isNotNull().extracting(Response::status).isEqualTo(200); - } - - @Test - void testUnknownTypeFile() throws Exception { - val path = Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.abc").toURI()); - assertThat(path).exists(); - - assertThat(API.uploadUnknownType(path.toFile())).isEqualTo("application/octet-stream"); - } - - @Test - void testFormData() throws Exception { - val formData = new FormData("application/custom-type", "popa.txt", "Allo".getBytes("UTF-8")); - - assertThat(API.uploadFormData(formData)).isEqualTo("popa.txt:application/custom-type"); - } - - @Test - void testSubmitRepeatableQueryParam() throws Exception { - val names = new String[]{"Milada", "Thais"}; - val stringResponse = API.submitRepeatableQueryParam(names); - assertThat(stringResponse).isEqualTo("Milada and Thais"); - } - - @Test - void testSubmitRepeatableFormParam() throws Exception { - val names = Arrays.asList("Milada", "Thais"); - val stringResponse = API.submitRepeatableFormParam(names); - assertThat(stringResponse).isEqualTo("Milada and Thais"); - } + private static final TestClient API; + + static { + API = Feign.builder().encoder(new FormEncoder(new JacksonEncoder())) + .logger(new JavaLogger(BasicClientTest.class).appendToFile("log.txt")).logLevel(FULL) + .target(TestClient.class, "http://localhost:8080"); + } + + @Test + void testForm() { + assertThat(API.form("1", "1")).isNotNull().extracting(Response::status).isEqualTo(200); + } + + @Test + void testFormException() { + assertThat(API.form("1", "2")).isNotNull().extracting(Response::status).isEqualTo(400); + } + + @Test + void testUpload() throws Exception { + val path = + Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); + assertThat(path).exists(); + + assertThat(API.upload(path.toFile())).asLong().isEqualTo(Files.size(path)); + } + + @Test + void testUploadWithParam() throws Exception { + val path = + Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); + assertThat(path).exists(); + + assertThat(API.upload(10, Boolean.TRUE, path.toFile())).asLong().isEqualTo(Files.size(path)); + } + + @Test + void testJson() { + val dto = new Dto("Artem", 11); + + assertThat(API.json(dto)).isEqualTo("ok"); + } + + @Test + void testQueryMap() { + Map value = + singletonMap("filter", (Object) asList("one", "two", "three", "four")); + + assertThat(API.queryMap(value)).isEqualTo("4"); + } + + @Test + void testMultipleFilesArray() throws Exception { + val path1 = + Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); + assertThat(path1).exists(); + + val path2 = Paths.get( + Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); + assertThat(path2).exists(); + + assertThat(API.uploadWithArray(new File[] {path1.toFile(), path2.toFile()})).asLong() + .isEqualTo(Files.size(path1) + Files.size(path2)); + } + + @Test + void testMultipleFilesList() throws Exception { + val path1 = + Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); + assertThat(path1).exists(); + + val path2 = Paths.get( + Thread.currentThread().getContextClassLoader().getResource("another_file.txt").toURI()); + assertThat(path2).exists(); + + assertThat(API.uploadWithList(asList(path1.toFile(), path2.toFile()))).asLong() + .isEqualTo(Files.size(path1) + Files.size(path2)); + } + + @Test + void testUploadWithDto() throws Exception { + val dto = new Dto("Artem", 11); + + val path = + Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.txt").toURI()); + assertThat(path).exists(); + + assertThat(API.uploadWithDto(dto, path.toFile())).isNotNull().extracting(Response::status) + .isEqualTo(200); + } + + @Test + void testUnknownTypeFile() throws Exception { + val path = + Paths.get(Thread.currentThread().getContextClassLoader().getResource("file.abc").toURI()); + assertThat(path).exists(); + + assertThat(API.uploadUnknownType(path.toFile())).isEqualTo("application/octet-stream"); + } + + @Test + void testFormData() throws Exception { + val formData = new FormData("application/custom-type", "popa.txt", "Allo".getBytes("UTF-8")); + + assertThat(API.uploadFormData(formData)).isEqualTo("popa.txt:application/custom-type"); + } + + @Test + void testSubmitRepeatableQueryParam() throws Exception { + val names = new String[] {"Milada", "Thais"}; + val stringResponse = API.submitRepeatableQueryParam(names); + assertThat(stringResponse).isEqualTo("Milada and Thais"); + } + + @Test + void testSubmitRepeatableFormParam() throws Exception { + val names = Arrays.asList("Milada", "Thais"); + val stringResponse = API.submitRepeatableFormParam(names); + assertThat(stringResponse).isEqualTo("Milada and Thais"); + } } diff --git a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java index 04e5275f2..f81f1948d 100644 --- a/feign-form/src/test/java/feign/form/ByteArrayClientTest.java +++ b/feign-form/src/test/java/feign/form/ByteArrayClientTest.java @@ -1,29 +1,24 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.Logger.Level.FULL; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import lombok.val; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; - import feign.Feign; import feign.Headers; import feign.Logger.JavaLogger; @@ -35,27 +30,28 @@ @SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class) class ByteArrayClientTest { - private static final CustomClient API; + private static final CustomClient API; - static { - val encoder = new FormEncoder(new JacksonEncoder()); + static { + val encoder = new FormEncoder(new JacksonEncoder()); - API = Feign.builder().encoder(encoder) - .logger(new JavaLogger(ByteArrayClientTest.class).appendToFile("log-byte.txt")).logLevel(FULL) - .target(CustomClient.class, "http://localhost:8080"); - } + API = Feign.builder().encoder(encoder) + .logger(new JavaLogger(ByteArrayClientTest.class).appendToFile("log-byte.txt")) + .logLevel(FULL) + .target(CustomClient.class, "http://localhost:8080"); + } - @Test - void testNotTreatedAsFileUpload() { - byte[] bytes = "Hello World".getBytes(); + @Test + void testNotTreatedAsFileUpload() { + byte[] bytes = "Hello World".getBytes(); - assertThat(API.uploadByteArray(bytes)).isNotNull().extracting(Response::status).isEqualTo(200); - } + assertThat(API.uploadByteArray(bytes)).isNotNull().extracting(Response::status).isEqualTo(200); + } - interface CustomClient { + interface CustomClient { - @RequestLine("POST /upload/byte_array_parameter") - @Headers("Content-Type: multipart/form-data") - Response uploadByteArray(@Param("file") byte[] bytes); - } + @RequestLine("POST /upload/byte_array_parameter") + @Headers("Content-Type: multipart/form-data") + Response uploadByteArray(@Param("file") byte[] bytes); + } } diff --git a/feign-form/src/test/java/feign/form/CustomClientTest.java b/feign-form/src/test/java/feign/form/CustomClientTest.java index 90145cc4d..9faf01d4b 100644 --- a/feign-form/src/test/java/feign/form/CustomClientTest.java +++ b/feign-form/src/test/java/feign/form/CustomClientTest.java @@ -1,30 +1,25 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.Logger.Level.FULL; import static feign.form.ContentType.MULTIPART; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import lombok.val; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; - import feign.Feign; import feign.Headers; import feign.Logger.JavaLogger; @@ -38,37 +33,38 @@ @SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class) class CustomClientTest { - private static final CustomClient API; + private static final CustomClient API; - static { - val encoder = new FormEncoder(new JacksonEncoder()); - val processor = (MultipartFormContentProcessor) encoder.getContentProcessor(MULTIPART); - processor.addFirstWriter(new CustomByteArrayWriter()); + static { + val encoder = new FormEncoder(new JacksonEncoder()); + val processor = (MultipartFormContentProcessor) encoder.getContentProcessor(MULTIPART); + processor.addFirstWriter(new CustomByteArrayWriter()); - API = Feign.builder().encoder(encoder).logger(new JavaLogger(CustomClientTest.class).appendToFile("log.txt")) - .logLevel(FULL).target(CustomClient.class, "http://localhost:8080"); - } + API = Feign.builder().encoder(encoder) + .logger(new JavaLogger(CustomClientTest.class).appendToFile("log.txt")) + .logLevel(FULL).target(CustomClient.class, "http://localhost:8080"); + } - @Test - void test() { - assertThat(API.uploadByteArray(new byte[0])).isNotNull().isEqualTo("popa.txt"); - } + @Test + void test() { + assertThat(API.uploadByteArray(new byte[0])).isNotNull().isEqualTo("popa.txt"); + } - private static final class CustomByteArrayWriter extends ByteArrayWriter { + private static final class CustomByteArrayWriter extends ByteArrayWriter { - @Override - protected void write(Output output, String key, Object value) throws EncodeException { - writeFileMetadata(output, key, "popa.txt", null); + @Override + protected void write(Output output, String key, Object value) throws EncodeException { + writeFileMetadata(output, key, "popa.txt", null); - val bytes = (byte[]) value; - output.write(bytes); - } - } + val bytes = (byte[]) value; + output.write(bytes); + } + } - interface CustomClient { + interface CustomClient { - @RequestLine("POST /upload/byte_array") - @Headers("Content-Type: multipart/form-data") - String uploadByteArray(@Param("file") byte[] bytes); - } + @RequestLine("POST /upload/byte_array") + @Headers("Content-Type: multipart/form-data") + String uploadByteArray(@Param("file") byte[] bytes); + } } diff --git a/feign-form/src/test/java/feign/form/Dto.java b/feign-form/src/test/java/feign/form/Dto.java index 2ca27140a..ec4d01c15 100644 --- a/feign-form/src/test/java/feign/form/Dto.java +++ b/feign-form/src/test/java/feign/form/Dto.java @@ -1,25 +1,20 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static lombok.AccessLevel.PRIVATE; - import java.io.Serializable; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -31,9 +26,9 @@ @FieldDefaults(level = PRIVATE) class Dto implements Serializable { - private static final long serialVersionUID = 4743133513526293872L; + private static final long serialVersionUID = 4743133513526293872L; - String name; + String name; - Integer age; + Integer age; } diff --git a/feign-form/src/test/java/feign/form/FormDto.java b/feign-form/src/test/java/feign/form/FormDto.java index d22fd28a1..5a5168995 100644 --- a/feign-form/src/test/java/feign/form/FormDto.java +++ b/feign-form/src/test/java/feign/form/FormDto.java @@ -1,23 +1,19 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static lombok.AccessLevel.PRIVATE; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -29,8 +25,8 @@ @FieldDefaults(level = PRIVATE) public class FormDto { - @FormProperty("f_name") - String firstName; + @FormProperty("f_name") + String firstName; - Integer age; + Integer age; } diff --git a/feign-form/src/test/java/feign/form/FormPropertyTest.java b/feign-form/src/test/java/feign/form/FormPropertyTest.java index bcafac742..52004b08c 100644 --- a/feign-form/src/test/java/feign/form/FormPropertyTest.java +++ b/feign-form/src/test/java/feign/form/FormPropertyTest.java @@ -1,29 +1,24 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.Logger.Level.FULL; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import lombok.val; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; - import feign.Feign; import feign.Headers; import feign.Logger.JavaLogger; @@ -33,25 +28,25 @@ @SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class) class FormPropertyTest { - private static final FormClient API; + private static final FormClient API; - static { - API = Feign.builder().encoder(new FormEncoder(new JacksonEncoder())) - .logger(new JavaLogger(FormPropertyTest.class).appendToFile("log.txt")).logLevel(FULL) - .target(FormClient.class, "http://localhost:8080"); - } + static { + API = Feign.builder().encoder(new FormEncoder(new JacksonEncoder())) + .logger(new JavaLogger(FormPropertyTest.class).appendToFile("log.txt")).logLevel(FULL) + .target(FormClient.class, "http://localhost:8080"); + } - @Test - void test() { - val dto = new FormDto("Amigo", 23); + @Test + void test() { + val dto = new FormDto("Amigo", 23); - assertThat(API.postData(dto)).isEqualTo("Amigo=23"); - } + assertThat(API.postData(dto)).isEqualTo("Amigo=23"); + } - interface FormClient { + interface FormClient { - @RequestLine("POST /form-data") - @Headers("Content-Type: application/x-www-form-urlencoded") - String postData(FormDto dto); - } + @RequestLine("POST /form-data") + @Headers("Content-Type: application/x-www-form-urlencoded") + String postData(FormDto dto); + } } diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index 1cc6bfb1c..23a7b4b79 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -1,19 +1,16 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -25,11 +22,9 @@ import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; - import java.io.IOException; import java.util.Collection; import java.util.List; - import lombok.val; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.HttpStatus; @@ -49,150 +44,158 @@ @SuppressWarnings("checkstyle:DesignForExtension") public class Server { - @PostMapping("/form") - public ResponseEntity form(@RequestParam("key1") String key1, @RequestParam("key2") String key2) { - val status = !key1.equals(key2) ? BAD_REQUEST : OK; - return ResponseEntity.status(status).body(null); - } - - @PostMapping("/upload/{id}") - @ResponseStatus(OK) - public ResponseEntity upload(@PathVariable("id") Integer id, @RequestParam("public") Boolean isPublic, - @RequestParam("file") MultipartFile file) { - HttpStatus status; - if (id == null || id != 10) { - status = LOCKED; - } else if (isPublic == null || !isPublic) { - status = FORBIDDEN; - } else if (file.getSize() == 0) { - status = I_AM_A_TEAPOT; - } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { - status = CONFLICT; - } else { - status = OK; - } - return ResponseEntity.status(status).body(file.getSize()); - } - - @PostMapping("/upload") - public ResponseEntity upload(@RequestParam("file") MultipartFile file) { - HttpStatus status; - if (file.getSize() == 0) { - status = I_AM_A_TEAPOT; - } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { - status = CONFLICT; - } else { - status = OK; - } - return ResponseEntity.status(status).body(file.getSize()); - } - - @PostMapping("/upload/files") - public ResponseEntity upload(@RequestParam("files") MultipartFile[] files) { - HttpStatus status; - if (files[0].getSize() == 0 || files[1].getSize() == 0) { - status = I_AM_A_TEAPOT; - } else if (files[0].getOriginalFilename() == null || files[0].getOriginalFilename().trim().isEmpty() - || files[1].getOriginalFilename() == null || files[1].getOriginalFilename().trim().isEmpty()) { - status = CONFLICT; - } else { - status = OK; - } - return ResponseEntity.status(status).body(files[0].getSize() + files[1].getSize()); - } - - @PostMapping(path = "/json", consumes = APPLICATION_JSON_VALUE) - public ResponseEntity json(@RequestBody Dto dto) { - HttpStatus status; - if (!dto.getName().equals("Artem")) { - status = CONFLICT; - } else if (!dto.getAge().equals(11)) { - status = I_AM_A_TEAPOT; - } else { - status = OK; - } - return ResponseEntity.status(status).body("ok"); - } - - @PostMapping("/query_map") - public ResponseEntity queryMap(@RequestParam("filter") List filters) { - val status = filters != null && !filters.isEmpty() ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).body(filters.size()); - } - - @PostMapping(path = "/wild-card-map", consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity wildCardMap(@RequestParam("key1") String key1, @RequestParam("key2") String key2) { - val status = key1.equals(key2) ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).body(null); - } - - @PostMapping(path = "/upload/with_dto", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadWithDto(Dto dto, @RequestPart("file") MultipartFile file) throws IOException { - val status = dto != null && dto.getName().equals("Artem") ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).body(file.getSize()); - } - - @PostMapping(path = "/upload/byte_array", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadByteArray(@RequestPart("file") MultipartFile file) { - val status = file != null ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).body(file.getOriginalFilename()); - } - - @PostMapping(path = "/upload/byte_array_parameter", consumes = MULTIPART_FORM_DATA_VALUE) - // We just want the request because when there's a filename part of the - // Content-Disposition header spring - // will treat it as a file (available through getFile()) and when it doesn't - // have the filename part it's - // available in the parameter (getParameter()) - public ResponseEntity uploadByteArrayParameter(MultipartHttpServletRequest request) { - val status = request.getFile("file") == null && request.getParameter("file") != null ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).build(); - } - - @PostMapping(path = "/upload/unknown_type", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadUnknownType(@RequestPart("file") MultipartFile file) { - val status = file != null ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).body(file.getContentType()); - } - - @PostMapping(path = "/upload/form_data", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadFormData(@RequestPart("file") MultipartFile file) { - val status = file != null ? OK : I_AM_A_TEAPOT; - return ResponseEntity.status(status).body(file.getOriginalFilename() + ':' + file.getContentType()); - } - - @PostMapping(path = "/submit/url", consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity submitRepeatableQueryParam(@RequestParam("names") String[] names) { - val response = new StringBuilder(); - if (names != null && names.length == 2) { - response.append(names[0]).append(" and ").append(names[1]); - } - val status = response.length() > 0 ? OK : I_AM_A_TEAPOT; - - return ResponseEntity.status(status).body(response.toString()); - } - - @PostMapping(path = "/submit/form", consumes = MULTIPART_FORM_DATA_VALUE) - public ResponseEntity submitRepeatableFormParam(@RequestParam("names") Collection names) { - val response = new StringBuilder(); - if (names != null && names.size() == 2) { - val iterator = names.iterator(); - response.append(iterator.next()).append(" and ").append(iterator.next()); - } - val status = response.length() > 0 ? OK : I_AM_A_TEAPOT; - - return ResponseEntity.status(status).body(response.toString()); - } - - @PostMapping(path = "/form-data", consumes = APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity submitPostData(@RequestParam("f_name") String firstName, - @RequestParam("age") Integer age) { - val response = new StringBuilder(); - if (firstName != null && age != null) { - response.append(firstName).append("=").append(age); - } - val status = response.length() > 0 ? OK : I_AM_A_TEAPOT; - - return ResponseEntity.status(status).body(response.toString()); - } + @PostMapping("/form") + public ResponseEntity form(@RequestParam("key1") String key1, + @RequestParam("key2") String key2) { + val status = !key1.equals(key2) ? BAD_REQUEST : OK; + return ResponseEntity.status(status).body(null); + } + + @PostMapping("/upload/{id}") + @ResponseStatus(OK) + public ResponseEntity upload(@PathVariable("id") Integer id, + @RequestParam("public") Boolean isPublic, + @RequestParam("file") MultipartFile file) { + HttpStatus status; + if (id == null || id != 10) { + status = LOCKED; + } else if (isPublic == null || !isPublic) { + status = FORBIDDEN; + } else if (file.getSize() == 0) { + status = I_AM_A_TEAPOT; + } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { + status = CONFLICT; + } else { + status = OK; + } + return ResponseEntity.status(status).body(file.getSize()); + } + + @PostMapping("/upload") + public ResponseEntity upload(@RequestParam("file") MultipartFile file) { + HttpStatus status; + if (file.getSize() == 0) { + status = I_AM_A_TEAPOT; + } else if (file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { + status = CONFLICT; + } else { + status = OK; + } + return ResponseEntity.status(status).body(file.getSize()); + } + + @PostMapping("/upload/files") + public ResponseEntity upload(@RequestParam("files") MultipartFile[] files) { + HttpStatus status; + if (files[0].getSize() == 0 || files[1].getSize() == 0) { + status = I_AM_A_TEAPOT; + } else if (files[0].getOriginalFilename() == null + || files[0].getOriginalFilename().trim().isEmpty() + || files[1].getOriginalFilename() == null + || files[1].getOriginalFilename().trim().isEmpty()) { + status = CONFLICT; + } else { + status = OK; + } + return ResponseEntity.status(status).body(files[0].getSize() + files[1].getSize()); + } + + @PostMapping(path = "/json", consumes = APPLICATION_JSON_VALUE) + public ResponseEntity json(@RequestBody Dto dto) { + HttpStatus status; + if (!dto.getName().equals("Artem")) { + status = CONFLICT; + } else if (!dto.getAge().equals(11)) { + status = I_AM_A_TEAPOT; + } else { + status = OK; + } + return ResponseEntity.status(status).body("ok"); + } + + @PostMapping("/query_map") + public ResponseEntity queryMap(@RequestParam("filter") List filters) { + val status = filters != null && !filters.isEmpty() ? OK : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(filters.size()); + } + + @PostMapping(path = "/wild-card-map", consumes = APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity wildCardMap(@RequestParam("key1") String key1, + @RequestParam("key2") String key2) { + val status = key1.equals(key2) ? OK : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(null); + } + + @PostMapping(path = "/upload/with_dto", consumes = MULTIPART_FORM_DATA_VALUE) + public ResponseEntity uploadWithDto(Dto dto, @RequestPart("file") MultipartFile file) + throws IOException { + val status = dto != null && dto.getName().equals("Artem") ? OK : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(file.getSize()); + } + + @PostMapping(path = "/upload/byte_array", consumes = MULTIPART_FORM_DATA_VALUE) + public ResponseEntity uploadByteArray(@RequestPart("file") MultipartFile file) { + val status = file != null ? OK : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(file.getOriginalFilename()); + } + + @PostMapping(path = "/upload/byte_array_parameter", consumes = MULTIPART_FORM_DATA_VALUE) + // We just want the request because when there's a filename part of the + // Content-Disposition header spring + // will treat it as a file (available through getFile()) and when it doesn't + // have the filename part it's + // available in the parameter (getParameter()) + public ResponseEntity uploadByteArrayParameter(MultipartHttpServletRequest request) { + val status = request.getFile("file") == null && request.getParameter("file") != null ? OK + : I_AM_A_TEAPOT; + return ResponseEntity.status(status).build(); + } + + @PostMapping(path = "/upload/unknown_type", consumes = MULTIPART_FORM_DATA_VALUE) + public ResponseEntity uploadUnknownType(@RequestPart("file") MultipartFile file) { + val status = file != null ? OK : I_AM_A_TEAPOT; + return ResponseEntity.status(status).body(file.getContentType()); + } + + @PostMapping(path = "/upload/form_data", consumes = MULTIPART_FORM_DATA_VALUE) + public ResponseEntity uploadFormData(@RequestPart("file") MultipartFile file) { + val status = file != null ? OK : I_AM_A_TEAPOT; + return ResponseEntity.status(status) + .body(file.getOriginalFilename() + ':' + file.getContentType()); + } + + @PostMapping(path = "/submit/url", consumes = APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity submitRepeatableQueryParam(@RequestParam("names") String[] names) { + val response = new StringBuilder(); + if (names != null && names.length == 2) { + response.append(names[0]).append(" and ").append(names[1]); + } + val status = response.length() > 0 ? OK : I_AM_A_TEAPOT; + + return ResponseEntity.status(status).body(response.toString()); + } + + @PostMapping(path = "/submit/form", consumes = MULTIPART_FORM_DATA_VALUE) + public ResponseEntity submitRepeatableFormParam(@RequestParam("names") Collection names) { + val response = new StringBuilder(); + if (names != null && names.size() == 2) { + val iterator = names.iterator(); + response.append(iterator.next()).append(" and ").append(iterator.next()); + } + val status = response.length() > 0 ? OK : I_AM_A_TEAPOT; + + return ResponseEntity.status(status).body(response.toString()); + } + + @PostMapping(path = "/form-data", consumes = APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity submitPostData(@RequestParam("f_name") String firstName, + @RequestParam("age") Integer age) { + val response = new StringBuilder(); + if (firstName != null && age != null) { + response.append(firstName).append("=").append(age); + } + val status = response.length() > 0 ? OK : I_AM_A_TEAPOT; + + return ResponseEntity.status(status).body(response.toString()); + } } diff --git a/feign-form/src/test/java/feign/form/TestClient.java b/feign-form/src/test/java/feign/form/TestClient.java index 874864821..b2693ad75 100644 --- a/feign-form/src/test/java/feign/form/TestClient.java +++ b/feign-form/src/test/java/feign/form/TestClient.java @@ -1,26 +1,22 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import java.io.File; import java.util.Collection; import java.util.List; import java.util.Map; - import feign.Headers; import feign.Param; import feign.QueryMap; @@ -29,54 +25,56 @@ public interface TestClient { - @RequestLine("POST /form") - @Headers("Content-Type: application/x-www-form-urlencoded") - Response form(@Param("key1") String key1, @Param("key2") String key2); + @RequestLine("POST /form") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response form(@Param("key1") String key1, @Param("key2") String key2); - @RequestLine("POST /upload/{id}") - @Headers("Content-Type: multipart/form-data") - String upload(@Param("id") Integer id, @Param("public") Boolean isPublic, @Param("file") File file); + @RequestLine("POST /upload/{id}") + @Headers("Content-Type: multipart/form-data") + String upload(@Param("id") Integer id, + @Param("public") Boolean isPublic, + @Param("file") File file); - @RequestLine("POST /upload") - @Headers("Content-Type: multipart/form-data") - String upload(@Param("file") File file); + @RequestLine("POST /upload") + @Headers("Content-Type: multipart/form-data") + String upload(@Param("file") File file); - @RequestLine("POST /json") - @Headers("Content-Type: application/json") - String json(Dto dto); + @RequestLine("POST /json") + @Headers("Content-Type: application/json") + String json(Dto dto); - @RequestLine("POST /query_map") - String queryMap(@QueryMap Map value); + @RequestLine("POST /query_map") + String queryMap(@QueryMap Map value); - @RequestLine("POST /upload/files") - @Headers("Content-Type: multipart/form-data") - String uploadWithArray(@Param("files") File[] files); + @RequestLine("POST /upload/files") + @Headers("Content-Type: multipart/form-data") + String uploadWithArray(@Param("files") File[] files); - @RequestLine("POST /upload/files") - @Headers("Content-Type: multipart/form-data") - String uploadWithList(@Param("files") List files); + @RequestLine("POST /upload/files") + @Headers("Content-Type: multipart/form-data") + String uploadWithList(@Param("files") List files); - @RequestLine("POST /upload/files") - @Headers("Content-Type: multipart/form-data") - String uploadWithManyFiles(@Param("files") File file1, @Param("files") File file2); + @RequestLine("POST /upload/files") + @Headers("Content-Type: multipart/form-data") + String uploadWithManyFiles(@Param("files") File file1, @Param("files") File file2); - @RequestLine("POST /upload/with_dto") - @Headers("Content-Type: multipart/form-data") - Response uploadWithDto(@Param("1") Dto dto, @Param("file") File file); + @RequestLine("POST /upload/with_dto") + @Headers("Content-Type: multipart/form-data") + Response uploadWithDto(@Param("1") Dto dto, @Param("file") File file); - @RequestLine("POST /upload/unknown_type") - @Headers("Content-Type: multipart/form-data") - String uploadUnknownType(@Param("file") File file); + @RequestLine("POST /upload/unknown_type") + @Headers("Content-Type: multipart/form-data") + String uploadUnknownType(@Param("file") File file); - @RequestLine("POST /upload/form_data") - @Headers("Content-Type: multipart/form-data") - String uploadFormData(@Param("file") FormData formData); + @RequestLine("POST /upload/form_data") + @Headers("Content-Type: multipart/form-data") + String uploadFormData(@Param("file") FormData formData); - @RequestLine("POST /submit/url") - @Headers("Content-Type: application/x-www-form-urlencoded") - String submitRepeatableQueryParam(@Param("names") String[] names); + @RequestLine("POST /submit/url") + @Headers("Content-Type: application/x-www-form-urlencoded") + String submitRepeatableQueryParam(@Param("names") String[] names); - @RequestLine("POST /submit/form") - @Headers("Content-Type: multipart/form-data") - String submitRepeatableFormParam(@Param("names") Collection names); + @RequestLine("POST /submit/form") + @Headers("Content-Type: multipart/form-data") + String submitRepeatableFormParam(@Param("names") Collection names); } diff --git a/feign-form/src/test/java/feign/form/WildCardMapTest.java b/feign-form/src/test/java/feign/form/WildCardMapTest.java index e61b30f57..423cb2f62 100644 --- a/feign-form/src/test/java/feign/form/WildCardMapTest.java +++ b/feign-form/src/test/java/feign/form/WildCardMapTest.java @@ -1,32 +1,26 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form; import static feign.Logger.Level.FULL; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - import java.util.HashMap; import java.util.Map; - import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; - import feign.Feign; import feign.Headers; import feign.Logger.JavaLogger; @@ -36,50 +30,50 @@ @SpringBootTest(webEnvironment = DEFINED_PORT, classes = Server.class) class WildCardMapTest { - private static FormUrlEncodedApi api; + private static FormUrlEncodedApi api; - @BeforeAll - static void configureClient() { - api = Feign.builder().encoder(new FormEncoder()) - .logger(new JavaLogger(WildCardMapTest.class).appendToFile("log.txt")).logLevel(FULL) - .target(FormUrlEncodedApi.class, "http://localhost:8080"); - } + @BeforeAll + static void configureClient() { + api = Feign.builder().encoder(new FormEncoder()) + .logger(new JavaLogger(WildCardMapTest.class).appendToFile("log.txt")).logLevel(FULL) + .target(FormUrlEncodedApi.class, "http://localhost:8080"); + } - @Test - void testOk() { - Map param = new HashMap() { + @Test + void testOk() { + Map param = new HashMap() { - private static final long serialVersionUID = 3109256773218160485L; + private static final long serialVersionUID = 3109256773218160485L; - { - put("key1", "1"); - put("key2", "1"); - } - }; + { + put("key1", "1"); + put("key2", "1"); + } + }; - assertThat(api.wildCardMap(param)).isNotNull().extracting(Response::status).isEqualTo(200); - } + assertThat(api.wildCardMap(param)).isNotNull().extracting(Response::status).isEqualTo(200); + } - @Test - void testBadRequest() { - Map param = new HashMap() { + @Test + void testBadRequest() { + Map param = new HashMap() { - private static final long serialVersionUID = 3109256773218160485L; + private static final long serialVersionUID = 3109256773218160485L; - { + { - put("key1", "1"); - put("key2", "2"); - } - }; + put("key1", "1"); + put("key2", "2"); + } + }; - assertThat(api.wildCardMap(param)).isNotNull().extracting(Response::status).isEqualTo(418); - } + assertThat(api.wildCardMap(param)).isNotNull().extracting(Response::status).isEqualTo(418); + } - interface FormUrlEncodedApi { + interface FormUrlEncodedApi { - @RequestLine("POST /wild-card-map") - @Headers("Content-Type: application/x-www-form-urlencoded") - Response wildCardMap(Map param); - } + @RequestLine("POST /wild-card-map") + @Headers("Content-Type: application/x-www-form-urlencoded") + Response wildCardMap(Map param); + } } diff --git a/feign-form/src/test/java/feign/form/issues/Issue63Test.java b/feign-form/src/test/java/feign/form/issues/Issue63Test.java index d372b41ee..649c74600 100644 --- a/feign-form/src/test/java/feign/form/issues/Issue63Test.java +++ b/feign-form/src/test/java/feign/form/issues/Issue63Test.java @@ -1,30 +1,24 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.issues; import static org.assertj.core.api.Assertions.assertThat; - import java.util.HashMap; import java.util.Map; - import io.undertow.server.HttpServerExchange; import lombok.val; import org.junit.jupiter.api.Test; - import feign.Feign; import feign.Headers; import feign.RequestLine; @@ -35,36 +29,37 @@ // https://github.com/OpenFeign/feign-form/issues/63 class Issue63Test { - @Test - void test() { - try (val server = UndertowServer.builder().callback(this::handleRequest).start()) { - val client = Feign.builder().encoder(new FormEncoder(new JacksonEncoder())).target(Client.class, - server.getConnectUrl()); + @Test + void test() { + try (val server = UndertowServer.builder().callback(this::handleRequest).start()) { + val client = + Feign.builder().encoder(new FormEncoder(new JacksonEncoder())).target(Client.class, + server.getConnectUrl()); - val data = new HashMap(); - data.put("from", "+987654321"); - data.put("to", "+123456789"); - data.put("body", "hello world"); + val data = new HashMap(); + data.put("from", "+987654321"); + data.put("to", "+123456789"); + data.put("body", "hello world"); - assertThat(client.map(data)).isEqualTo("ok"); - } - } + assertThat(client.map(data)).isEqualTo("ok"); + } + } - private void handleRequest(HttpServerExchange exchange, byte[] message) { - // assert request - assertThat(exchange.getRequestHeaders().getFirst(io.undertow.util.Headers.CONTENT_TYPE)) - .isEqualTo("application/x-www-form-urlencoded; charset=UTF-8"); - assertThat(message).asString().isEqualTo("from=%2B987654321&to=%2B123456789&body=hello+world"); + private void handleRequest(HttpServerExchange exchange, byte[] message) { + // assert request + assertThat(exchange.getRequestHeaders().getFirst(io.undertow.util.Headers.CONTENT_TYPE)) + .isEqualTo("application/x-www-form-urlencoded; charset=UTF-8"); + assertThat(message).asString().isEqualTo("from=%2B987654321&to=%2B123456789&body=hello+world"); - // build response - exchange.getResponseHeaders().put(io.undertow.util.Headers.CONTENT_TYPE, "text/plain"); - exchange.getResponseSender().send("ok"); - } + // build response + exchange.getResponseHeaders().put(io.undertow.util.Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender().send("ok"); + } - interface Client { + interface Client { - @RequestLine("POST") - @Headers("Content-Type: application/x-www-form-urlencoded; charset=utf-8") - String map(Map data); - } + @RequestLine("POST") + @Headers("Content-Type: application/x-www-form-urlencoded; charset=utf-8") + String map(Map data); + } } diff --git a/feign-form/src/test/java/feign/form/utils/UndertowServer.java b/feign-form/src/test/java/feign/form/utils/UndertowServer.java index 04ec22a71..63d70bd2f 100644 --- a/feign-form/src/test/java/feign/form/utils/UndertowServer.java +++ b/feign-form/src/test/java/feign/form/utils/UndertowServer.java @@ -1,23 +1,19 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2012-2024 The Feign Authors * - * 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 + * 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 + * 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. + * 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 feign.form.utils; import java.net.InetSocketAddress; - import io.appulse.utils.SocketUtils; import io.undertow.Undertow; import io.undertow.io.Receiver.FullBytesCallback; @@ -31,55 +27,56 @@ public final class UndertowServer implements AutoCloseable { - private final Undertow undertow; - - @Builder(buildMethodName = "start") - private UndertowServer(FullBytesCallback callback) { - val port = SocketUtils.findFreePort() - .orElseThrow(() -> new IllegalStateException("no available port to start server")); - - undertow = Undertow.builder().addHttpListener(port, "localhost") - .setHandler(new BlockingHandler(new ReadAllBytesHandler(callback))).build(); - - undertow.start(); - } - - /** - * Returns server connect URL. - * - * @return listining server's url. - */ - public String getConnectUrl() { - val listenerInfo = undertow.getListenerInfo().iterator().next(); - - val address = (InetSocketAddress) listenerInfo.getAddress(); - - return String.format("%s://%s:%d", listenerInfo.getProtcol(), address.getHostString(), address.getPort()); - } - - @Override - public void close() { - undertow.stop(); - } - - @RequiredArgsConstructor - private static final class ReadAllBytesHandler implements HttpHandler { - - private final FullBytesCallback callback; - - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception { - exchange.getRequestReceiver().receiveFullBytes(this::handleBytes); - } - - private void handleBytes(HttpServerExchange exchange, byte[] message) { - try { - callback.handle(exchange, message); - } catch (Throwable ex) { - exchange.setStatusCode(500); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); - exchange.getResponseSender().send(ex.getMessage()); - } - } - } + private final Undertow undertow; + + @Builder(buildMethodName = "start") + private UndertowServer(FullBytesCallback callback) { + val port = SocketUtils.findFreePort() + .orElseThrow(() -> new IllegalStateException("no available port to start server")); + + undertow = Undertow.builder().addHttpListener(port, "localhost") + .setHandler(new BlockingHandler(new ReadAllBytesHandler(callback))).build(); + + undertow.start(); + } + + /** + * Returns server connect URL. + * + * @return listining server's url. + */ + public String getConnectUrl() { + val listenerInfo = undertow.getListenerInfo().iterator().next(); + + val address = (InetSocketAddress) listenerInfo.getAddress(); + + return String.format("%s://%s:%d", listenerInfo.getProtcol(), address.getHostString(), + address.getPort()); + } + + @Override + public void close() { + undertow.stop(); + } + + @RequiredArgsConstructor + private static final class ReadAllBytesHandler implements HttpHandler { + + private final FullBytesCallback callback; + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + exchange.getRequestReceiver().receiveFullBytes(this::handleBytes); + } + + private void handleBytes(HttpServerExchange exchange, byte[] message) { + try { + callback.handle(exchange, message); + } catch (Throwable ex) { + exchange.setStatusCode(500); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender().send(ex.getMessage()); + } + } + } } diff --git a/spring4/pom.xml b/spring4/pom.xml index c8f9d06e7..729d70aad 100644 --- a/spring4/pom.xml +++ b/spring4/pom.xml @@ -24,9 +24,9 @@ feign-spring4 + pom Feign Spring4 Feign Contracts for Spring4 - pom ${project.basedir}/.. From 93f25615e2a8efd1158c12c48a0e5712362cc759 Mon Sep 17 00:00:00 2001 From: Marvin Froeder Date: Sat, 21 Sep 2024 01:18:34 -0300 Subject: [PATCH 92/93] Update rewrite recipes --- pom.xml | 1 + spring4/pom.xml | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index e28bcd878..55183b52f 100644 --- a/pom.xml +++ b/pom.xml @@ -980,6 +980,7 @@ true org.openrewrite.java.testing.hamcrest.MigrateHamcrestToAssertJ + org.openrewrite.java.testing.junit5.AssertToAssertions org.openrewrite.java.testing.assertj.JUnitToAssertj org.openrewrite.java.testing.assertj.Assertj org.openrewrite.java.migrate.UpgradeToJava21 diff --git a/spring4/pom.xml b/spring4/pom.xml index 729d70aad..063b90ffe 100644 --- a/spring4/pom.xml +++ b/spring4/pom.xml @@ -39,4 +39,12 @@ feign-spring + + + + io.github.openfeign + feign-spring + ${project.version} + + From 12c56e062666267953614b6f8278c0aa5f290c14 Mon Sep 17 00:00:00 2001 From: Marvin Froeder Date: Sat, 21 Sep 2024 01:29:31 -0300 Subject: [PATCH 93/93] Remove needless file --- .mvn/wrapper/MavenWrapperDownloader.java | 98 ------------------------ 1 file changed, 98 deletions(-) delete mode 100755 .mvn/wrapper/MavenWrapperDownloader.java diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100755 index 776860764..000000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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. - */ - - import java.io.IOException; - import java.io.InputStream; - import java.net.Authenticator; - import java.net.PasswordAuthentication; - import java.net.URL; - import java.nio.file.Files; - import java.nio.file.Path; - import java.nio.file.Paths; - import java.nio.file.StandardCopyOption; - - public final class MavenWrapperDownloader - { - private static final String WRAPPER_VERSION = "@@project.version@@"; - - private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); - - public static void main( String[] args ) - { - log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); - - if ( args.length != 2 ) - { - System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); - System.exit( 1 ); - } - - try - { - log( " - Downloader started" ); - final URL wrapperUrl = new URL( args[0] ); - final String jarPath = args[1].replace( "..", "" ); // Sanitize path - final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); - downloadFileFromURL( wrapperUrl, wrapperJarPath ); - log( "Done" ); - } - catch ( IOException e ) - { - System.err.println( "- Error downloading: " + e.getMessage() ); - if ( VERBOSE ) - { - e.printStackTrace(); - } - System.exit( 1 ); - } - } - - private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) - throws IOException - { - log( " - Downloading to: " + wrapperJarPath ); - if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) - { - final String username = System.getenv( "MVNW_USERNAME" ); - final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); - Authenticator.setDefault( new Authenticator() - { - @Override - protected PasswordAuthentication getPasswordAuthentication() - { - return new PasswordAuthentication( username, password ); - } - } ); - } - try ( InputStream inStream = wrapperUrl.openStream() ) - { - Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); - } - log( " - Downloader complete" ); - } - - private static void log( String msg ) - { - if ( VERBOSE ) - { - System.out.println( msg ); - } - } - - }