Marvin Froeder
2 years ago
committed by
GitHub
8 changed files with 466 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||||
|
Wikipedia Example with Springboot |
||||||
|
================================= |
||||||
|
|
||||||
|
This is an example of advanced json response parsing, including pagination. |
@ -0,0 +1,118 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!-- |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
--> |
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
|
||||||
|
<parent> |
||||||
|
<groupId>io.github.openfeign</groupId> |
||||||
|
<artifactId>parent</artifactId> |
||||||
|
<version>12.0-SNAPSHOT</version> |
||||||
|
</parent> |
||||||
|
|
||||||
|
<groupId>io.github.openfeign</groupId> |
||||||
|
<artifactId>feign-example-wikipedia-with-springboot</artifactId> |
||||||
|
<packaging>jar</packaging> |
||||||
|
<name>Wikipedia Example</name> |
||||||
|
|
||||||
|
<properties> |
||||||
|
<main.basedir>${project.basedir}/..</main.basedir> |
||||||
|
</properties> |
||||||
|
|
||||||
|
<dependencyManagement> |
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.cloud</groupId> |
||||||
|
<artifactId>spring-cloud-dependencies</artifactId> |
||||||
|
<version>2021.0.4</version> |
||||||
|
<type>pom</type> |
||||||
|
<scope>import</scope> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
</dependencyManagement> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>io.github.openfeign</groupId> |
||||||
|
<artifactId>feign-core</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>io.github.openfeign</groupId> |
||||||
|
<artifactId>feign-gson</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.cloud</groupId> |
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.apache.commons</groupId> |
||||||
|
<artifactId>commons-exec</artifactId> |
||||||
|
<version>1.3</version> |
||||||
|
<scope>test</scope> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
|
||||||
|
<build> |
||||||
|
<defaultGoal>package</defaultGoal> |
||||||
|
<plugins> |
||||||
|
<plugin> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||||
|
<configuration> |
||||||
|
<mainClass>example.wikipedia.WikipediaApplication</mainClass> |
||||||
|
<layout>ZIP</layout> |
||||||
|
</configuration> |
||||||
|
<executions> |
||||||
|
<execution> |
||||||
|
<goals> |
||||||
|
<goal>repackage</goal> |
||||||
|
</goals> |
||||||
|
</execution> |
||||||
|
</executions> |
||||||
|
</plugin> |
||||||
|
|
||||||
|
<plugin> |
||||||
|
<groupId>org.skife.maven</groupId> |
||||||
|
<artifactId>really-executable-jar-maven-plugin</artifactId> |
||||||
|
<version>1.5.0</version> |
||||||
|
<configuration> |
||||||
|
<programFile>wikipedia</programFile> |
||||||
|
</configuration> |
||||||
|
<executions> |
||||||
|
<execution> |
||||||
|
<phase>package</phase> |
||||||
|
<goals> |
||||||
|
<goal>really-executable-jar</goal> |
||||||
|
</goals> |
||||||
|
</execution> |
||||||
|
</executions> |
||||||
|
</plugin> |
||||||
|
<plugin> |
||||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||||
|
<artifactId>maven-failsafe-plugin</artifactId> |
||||||
|
<version>${maven-surefire-plugin.version}</version> |
||||||
|
<executions> |
||||||
|
<execution> |
||||||
|
<goals> |
||||||
|
<goal>integration-test</goal> |
||||||
|
<goal>verify</goal> |
||||||
|
</goals> |
||||||
|
</execution> |
||||||
|
</executions> |
||||||
|
</plugin> |
||||||
|
</plugins> |
||||||
|
</build> |
||||||
|
</project> |
@ -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<X> extends TypeAdapter<WikipediaClient.Response<X>> { |
||||||
|
|
||||||
|
/** |
||||||
|
* 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. |
||||||
|
* <p/> |
||||||
|
* <br> |
||||||
|
* 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}. |
||||||
|
* <p/> |
||||||
|
* |
||||||
|
* <pre> |
||||||
|
* "pages": { |
||||||
|
* "2576129": { |
||||||
|
* "pageid": 2576129, |
||||||
|
* "title": "Burchell's zebra", |
||||||
|
* --snip-- |
||||||
|
* </pre> |
||||||
|
*/ |
||||||
|
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<X> read(JsonReader reader) throws IOException { |
||||||
|
WikipediaClient.Response<X> pages = new WikipediaClient.Response<X>(); |
||||||
|
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<X> response) throws IOException { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
} |
@ -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<Page> 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<Page> lazySearch(final Wikipedia wikipedia, final String query) { |
||||||
|
final Response<Page> first = wikipedia.search(query); |
||||||
|
if (first.nextOffset == null) { |
||||||
|
return first.iterator(); |
||||||
|
} |
||||||
|
return new Iterator<Page>() { |
||||||
|
Iterator<Page> 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<Page> 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(); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -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<Page> 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<Page> resumeSearch(@PathVariable("search") String search, |
||||||
|
@PathVariable("offset") long offset); |
||||||
|
} |
||||||
|
|
||||||
|
static class Page { |
||||||
|
|
||||||
|
long id; |
||||||
|
String title; |
||||||
|
} |
||||||
|
|
||||||
|
public static class Response<X> extends ArrayList<X> { |
||||||
|
|
||||||
|
/** |
||||||
|
* when present, the position to resume the list. |
||||||
|
*/ |
||||||
|
Long nextOffset; |
||||||
|
} |
||||||
|
} |
@ -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<Page> pagesAdapter = new ResponseAdapter<Page>() { |
||||||
|
|
||||||
|
@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<Response<Page>>() {}.getType(), pagesAdapter) |
||||||
|
.create(); |
||||||
|
|
||||||
|
return new GsonDecoder(gson); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue