diff --git a/example-wikipedia-with-springboot/README.md b/example-wikipedia-with-springboot/README.md new file mode 100644 index 000000000..939fe0f91 --- /dev/null +++ b/example-wikipedia-with-springboot/README.md @@ -0,0 +1,4 @@ +Wikipedia Example with Springboot +================================= + +This is an example of advanced json response parsing, including pagination. diff --git a/example-wikipedia-with-springboot/pom.xml b/example-wikipedia-with-springboot/pom.xml new file mode 100644 index 000000000..e12afa576 --- /dev/null +++ b/example-wikipedia-with-springboot/pom.xml @@ -0,0 +1,118 @@ + + + + 4.0.0 + + + io.github.openfeign + parent + 12.0-SNAPSHOT + + + io.github.openfeign + feign-example-wikipedia-with-springboot + jar + Wikipedia Example + + + ${project.basedir}/.. + + + + + + org.springframework.cloud + spring-cloud-dependencies + 2021.0.4 + pom + import + + + + + + + io.github.openfeign + feign-core + + + io.github.openfeign + feign-gson + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.apache.commons + commons-exec + 1.3 + test + + + + + package + + + org.springframework.boot + spring-boot-maven-plugin + + example.wikipedia.WikipediaApplication + ZIP + + + + + repackage + + + + + + + org.skife.maven + really-executable-jar-maven-plugin + 1.5.0 + + wikipedia + + + + package + + really-executable-jar + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-surefire-plugin.version} + + + + integration-test + verify + + + + + + + diff --git a/example-wikipedia-with-springboot/src/main/java/example/wikipedia/ResponseAdapter.java b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/ResponseAdapter.java new file mode 100644 index 000000000..78cb8810b --- /dev/null +++ b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/ResponseAdapter.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2022 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 + * + * 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 example.wikipedia; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +abstract class ResponseAdapter extends TypeAdapter> { + + /** + * name of the key inside the {@code query} dict which holds the elements desired. ex. {@code + * pages}. + */ + protected abstract String query(); + + /** + * Parses the contents of a result object. + *

+ *
+ * ex. If {@link #query()} is {@code pages}, then this would parse the value of each key in the + * dict {@code pages}. In the example below, this would first start at line {@code 3}. + *

+ * + *

+   * "pages": {
+   *   "2576129": {
+   *     "pageid": 2576129,
+   *     "title": "Burchell's zebra",
+   * --snip--
+   * 
+ */ + protected abstract X build(JsonReader reader) throws IOException; + + /** + * the wikipedia api doesn't use json arrays, rather a series of nested objects. + */ + @Override + public WikipediaClient.Response read(JsonReader reader) throws IOException { + WikipediaClient.Response pages = new WikipediaClient.Response(); + reader.beginObject(); + while (reader.hasNext()) { + String nextName = reader.nextName(); + if ("query".equals(nextName)) { + reader.beginObject(); + while (reader.hasNext()) { + if (query().equals(reader.nextName())) { + reader.beginObject(); + while (reader.hasNext()) { + // each element is in form: "id" : { object } + // this advances the pointer to the value and skips the key + reader.nextName(); + reader.beginObject(); + pages.add(build(reader)); + reader.endObject(); + } + reader.endObject(); + } else { + reader.skipValue(); + } + } + reader.endObject(); + } else if ("continue".equals(nextName)) { + reader.beginObject(); + while (reader.hasNext()) { + if ("gsroffset".equals(reader.nextName())) { + pages.nextOffset = reader.nextLong(); + } else { + reader.skipValue(); + } + } + reader.endObject(); + } else { + reader.skipValue(); + } + } + reader.endObject(); + return pages; + } + + @Override + public void write(JsonWriter out, WikipediaClient.Response response) throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaApplication.java b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaApplication.java new file mode 100644 index 000000000..c91debf10 --- /dev/null +++ b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaApplication.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2022 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 + * + * 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 example.wikipedia; + +import example.wikipedia.WikipediaClient.Page; +import example.wikipedia.WikipediaClient.Response; +import example.wikipedia.WikipediaClient.Wikipedia; +import java.util.Iterator; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableFeignClients +public class WikipediaApplication { + + public static void main(String[] args) { + SpringApplication.run(WikipediaApplication.class, args).start();; + } + + @Autowired + private Wikipedia wikipedia; + + @PostConstruct + public void run() { + System.out.println("Let's search for PTAL!"); + Iterator pages = lazySearch(wikipedia, "PTAL"); + while (pages.hasNext()) { + System.out.println(pages.next().title); + } + } + + /** + * this will lazily continue searches, making new http calls as necessary. + * + * @param wikipedia used to search + * @param query see {@link Wikipedia#search(String)}. + */ + static Iterator lazySearch(final Wikipedia wikipedia, final String query) { + final Response first = wikipedia.search(query); + if (first.nextOffset == null) { + return first.iterator(); + } + return new Iterator() { + Iterator current = first.iterator(); + Long nextOffset = first.nextOffset; + + @Override + public boolean hasNext() { + while (!current.hasNext() && nextOffset != null) { + System.out.println("Wow.. even more results than " + nextOffset); + Response nextPage = wikipedia.resumeSearch(query, nextOffset); + current = nextPage.iterator(); + nextOffset = nextPage.nextOffset; + } + return current.hasNext(); + } + + @Override + public Page next() { + return current.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaClient.java b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaClient.java new file mode 100644 index 000000000..011699384 --- /dev/null +++ b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaClient.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2022 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 + * + * 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 example.wikipedia; + +import java.util.ArrayList; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +public class WikipediaClient { + + + + @FeignClient(value = "jplaceholder", url = "https://en.wikipedia.org/", + configuration = WikipediaClientConfiguration.class) + public static interface Wikipedia { + + + @RequestMapping(method = RequestMethod.GET, + value = "/w/api.php?action=query&continue=&generator=search&prop=info&format=json&gsrsearch={search}") + Response search(@PathVariable("search") String search); + + + @RequestMapping(method = RequestMethod.GET, + value = "/w/api.php?action=query&continue=&generator=search&prop=info&format=json&gsrsearch={search}&gsroffset={offset}") + Response resumeSearch(@PathVariable("search") String search, + @PathVariable("offset") long offset); + } + + static class Page { + + long id; + String title; + } + + public static class Response extends ArrayList { + + /** + * when present, the position to resume the list. + */ + Long nextOffset; + } +} diff --git a/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaClientConfiguration.java b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaClientConfiguration.java new file mode 100644 index 000000000..b075a8c99 --- /dev/null +++ b/example-wikipedia-with-springboot/src/main/java/example/wikipedia/WikipediaClientConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2022 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 + * + * 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 example.wikipedia; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import example.wikipedia.WikipediaClient.Page; +import example.wikipedia.WikipediaClient.Response; +import feign.codec.Decoder; +import feign.gson.GsonDecoder; +import java.io.IOException; +import org.springframework.context.annotation.Bean; + +public class WikipediaClientConfiguration { + + static ResponseAdapter pagesAdapter = new ResponseAdapter() { + + @Override + protected String query() { + return "pages"; + } + + @Override + protected Page build(JsonReader reader) throws IOException { + Page page = new Page(); + while (reader.hasNext()) { + String key = reader.nextName(); + if (key.equals("pageid")) { + page.id = reader.nextLong(); + } else if (key.equals("title")) { + page.title = reader.nextString(); + } else { + reader.skipValue(); + } + } + return page; + } + }; + + + @Bean + public Decoder decoder() { + Gson gson = new GsonBuilder() + .registerTypeAdapter(new TypeToken>() {}.getType(), pagesAdapter) + .create(); + + return new GsonDecoder(gson); + } + +} diff --git a/example-wikipedia-with-springboot/src/test/java/feign/example/wikipedia/WikipediaExampleIT.java b/example-wikipedia-with-springboot/src/test/java/feign/example/wikipedia/WikipediaExampleIT.java new file mode 100644 index 000000000..3866cd537 --- /dev/null +++ b/example-wikipedia-with-springboot/src/test/java/feign/example/wikipedia/WikipediaExampleIT.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 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 + * + * 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.example.wikipedia; + +import static org.hamcrest.MatcherAssert.assertThat; +import java.io.File; +import java.util.Arrays; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.hamcrest.CoreMatchers; +import org.junit.Test; + +/** + * Run main for {@link WikipediaExampleIT} + */ +public class WikipediaExampleIT { + + @Test + public void runMain() throws Exception { + final String jar = Arrays.stream(new File("target").listFiles()) + .filter(file -> file.getName().startsWith("feign-example-wikipedia-with-springboot") + && file.getName().endsWith(".jar")) + .findFirst() + .map(File::getAbsolutePath) + .get(); + + final String line = "java -jar " + jar; + final CommandLine cmdLine = CommandLine.parse(line); + final int exitValue = new DefaultExecutor().execute(cmdLine); + + assertThat(exitValue, CoreMatchers.equalTo(0)); + } + +} diff --git a/pom.xml b/pom.xml index e8bb1999f..9a0d59baf 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,7 @@ example-github example-github-with-coroutine example-wikipedia + example-wikipedia-with-springboot mock apt-test-generator benchmark