├── README.md
├── build.sbt
├── project
├── bintray.sbt
├── build.properties
└── plugins.sbt
└── src
├── main
└── java
│ └── http
│ └── rest
│ ├── AbstractRestClient.java
│ ├── RequestInterceptor.java
│ ├── RestClient.java
│ ├── RestClientBuilder.java
│ └── RestClientException.java
└── test
├── java
└── test
│ ├── Assertions.java
│ ├── Settings.java
│ ├── integration
│ ├── CustomRestClientTest.java
│ ├── GoogleGeocodeTest.java
│ └── RequestInterceptorTest.java
│ └── unit
│ ├── BaseTest.java
│ └── TestRestClient.java
└── resources
└── geocode.json
/README.md:
--------------------------------------------------------------------------------
1 | http-rest-client
2 | ======================
3 |
4 | A simple & easy to use REST client written in Java and levarging the HttpClient 4.3 library.
5 |
6 | Important Note: This library only supports JSON payloads. You are more than welcome to add XML support and send me a pull request. I have no future plans to add XML support.
7 |
8 | Cheers!
9 |
10 | SBT / Maven Dependency
11 | -------------------------
12 |
13 | SBT
14 |
15 | resolvers += "java-utils" at "http://dl.bintray.com/g00dnatur3/java-utils/"
16 |
17 | libraryDependencies ++= Seq(
18 | "g00dnatur3" %% "http-rest-client" % "1.0.21"
19 | )
20 |
21 | Maven
22 |
23 |
24 | java-utils
25 | http://dl.bintray.com/g00dnatur3/java-utils/
26 |
27 |
28 |
29 | g00dnatur3
30 | http-rest-client_2.10
31 | 1.0.21
32 |
33 |
34 | Getting Started
35 | -------------------------
36 |
37 | This is the simplest way to construct a RestClient
38 |
39 | RestClient client = RestClient.builder().build();
40 |
41 |
42 | This example executes a GET request to the google geocoder api:
43 |
44 | RestClient client = RestClient.builder().build();
45 |
46 | String geocoderUrl = "http://maps.googleapis.com/maps/api/geocode/json";
47 |
48 | Map params = Maps.newHashMap();
49 | params.put("address", "1980 W. Bayshore Rd. 94303");
50 | params.put("sensor", "false");
51 |
52 | JsonNode node = client.get(geocoderUrl, params, JsonNode.class);
53 |
54 | Execute a GET Request to get a single object
55 | ------------------------------------------------------
56 |
57 | String url = ...
58 | Map queryParams = ...
59 |
60 | Person person = client.get(url, queryParams, Person.class);
61 |
62 | Execute a GET Request to get a list of objects
63 | ------------------------------------------------------
64 |
65 | String url = ...
66 | Map queryParams = ...
67 |
68 | List people = client.get(url, queryParams, new TypeReference>() {});
69 |
70 | Execute a POST Request on a single object
71 | ----------------------------------------------
72 |
73 | String url = ...
74 | Person person = ...
75 |
76 | Header header = client.create(url, person);
77 |
78 | if (header != null) {
79 | System.out.println("Location header is: " + header.value());
80 | }
81 |
82 | Execute a POST Request on a list of objects
83 | ----------------------------------------------
84 |
85 | String url = ...
86 | List people = ...
87 |
88 | Header header = client.create(url, people);
89 |
90 | if (header != null) {
91 | System.out.println("Location header is: " + header.value());
92 | }
93 |
94 | Execute a PUT Request on a single object
95 | ---------------------------------------------
96 |
97 | String url = ...
98 | Person person = ...
99 |
100 | client.update(url, person);
101 |
102 | Execute a PUT Request on a list of objects
103 | ---------------------------------------------
104 |
105 | String url = ...
106 | List people = ...
107 |
108 | client.update(url, people);
109 |
110 | Add Headers with RequestInterceptor
111 | ------------------------------------------------
112 |
113 | In order to add headers on a request you need to use the RequestInterceptor.
114 |
115 | This is an example of a RequestInterceptor that will "intercept" all requests sent from a RestClient and add an Authorization header to them.
116 |
117 | final String credentials = ...
118 | RequestInterceptor authorize = new RequestInterceptor() {
119 | @Override
120 | public void intercept(HttpRequestBase request) {
121 | request.addHeader("Authorization", credentials);
122 | }
123 | };
124 | RestClient client = RestClient.builder().requestInterceptor(authorize).build();
125 |
126 | All the `get`,`create`,`update`,`delete` methods are overloaded with a RequestInterceptor.
127 |
128 | This example of RequestInterceptor will "intercept" on a per request basis to add a header:
129 |
130 | public RequestInterceptor authorize(final String credentials) {
131 | return new RequestInterceptor() {
132 | @Override
133 | public void intercept(HttpUriRequest request) {
134 | request.addHeader("Authorization", credentials);
135 | }
136 | };
137 | }
138 |
139 | public Person getPerson(String credentials, url) {
140 | return client.get(authorize(credentials), url, null, Person.class); //queryParams=null
141 | }
142 |
143 | public Header createPerson(String credentials, Person person, url) {
144 | return client.create(authorize(credentials), url, person);
145 | }
146 |
147 | Look at `test.integration.RequestInterceptorTest` to see working examples.
148 |
149 | Configure the Request with RequestInterceptor
150 | ----------------------------------------------
151 |
152 | Similar with how we can add headers on a per request basis, we can also do some config on a per request basis.
153 |
154 | public RequestInterceptor connectTimeout(final int timeout) {
155 | return new RequestInterceptor() {
156 | @Override
157 | public void intercept(HttpUriRequest request) {
158 | request.setConfig(
159 | RequestConfig.custom()
160 | .setConnectTimeout(timeout).build());
161 | }
162 | };
163 | }
164 |
165 | client.create(connectTimeout(1000), url, person);
166 |
167 |
168 | Configuration (Proxy, SSL...)
169 | -------------------------
170 |
171 | The following system properties are taken into account by this library:
172 |
173 | ssl.TrustManagerFactory.algorithm
174 | javax.net.ssl.trustStoreType
175 | javax.net.ssl.trustStore
176 | javax.net.ssl.trustStoreProvider
177 | javax.net.ssl.trustStorePassword
178 | java.home
179 | ssl.KeyManagerFactory.algorithm
180 | javax.net.ssl.keyStoreType
181 | javax.net.ssl.keyStore
182 | javax.net.ssl.keyStoreProvider
183 | javax.net.ssl.keyStorePassword
184 | http.proxyHost
185 | http.proxyPort
186 | http.nonProxyHosts
187 | http.keepAlive
188 | http.maxConnections
189 |
190 | For example, if you wanted to configure the proxy you could do:
191 |
192 | System.setProperty("http.proxyHost","somehost.com")
193 | System.setProperty("http.proxyPort","8080")
194 |
195 | Handling failed Requests
196 | ------------------------------
197 | If your request fails, have no fear, you can get your hands on the HttpResonse and handle it however you need.
198 |
199 | The `get`,`create`,`update`,`delete` methods throw a RestClientException when they fail to execute.
200 |
201 | The RestClientException contains the actual "ready-to-consume" HttpResponse object.
202 |
203 | try {
204 | Person p = client.get(url, queryParams, Person.class)
205 | } catch (RestClientException e) {
206 |
207 | HttpResponse response = e.response();
208 | if (response != null) {
209 |
210 | // if the payload contains error information you can get it
211 | String errorInfo = client.contentAsString(response);
212 |
213 | client.consume(response); //closes the response
214 | }
215 | }
216 |
217 | Expected Response Status
218 | ------------------------------
219 |
220 | The default expected response status for `get`,`update`,`delete` is 200 and for `create` it's 201.
221 |
222 | The RestClient will throw a RestClientException if the actual response status is not equal to the expected status.
223 |
224 | All the methods in the RestClient are overloaded with `int expectedStatus` so you can specify the expected response status if it deviates from the default.
225 |
226 | For example, the `create` has a default expected status of 201. However, if the server sends back a 200 response instead, you can do the following:
227 |
228 | String url = ...
229 | Person person = ...
230 |
231 | try {
232 | Header header = client.create(url, person, 200);
233 | } catch (RestClientException e) {
234 | // here if the response status is not 200 or the request failed to execute.
235 | }
236 |
237 |
238 | Injecting your own HttpClient
239 | ------------------------------
240 |
241 | Because I am using the builder pattern, it is quite easy to inject your own HttpClient instance:
242 |
243 | HttpClient myHttpClient = ...
244 |
245 | RestClient.builder().httpClient(myHttpClient).build();
246 |
247 | Set Cookies with your own HttpClient
248 | -------------------------------------
249 |
250 | In order to set cookies, you need create your own HttpClient like so:
251 |
252 | BasicCookieStore cookieStore = new BasicCookieStore();
253 | BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", "1234");
254 | cookie.setDomain(".github.com");
255 | cookie.setPath("/");
256 | cookieStore.addCookie(cookie);
257 |
258 | //notice the useSystemPoperties() -> enables configuration (Proxy, SSL...) via system properties
259 | HttpClient myHttpClient = HttpClientBuilder.create()
260 | .setDefaultCookieStore(cookieStore)
261 | .useSystemProperties()
262 | .build();
263 |
264 | RestClient client = RestClient.builder().httpClient(myHttpClient).build();
265 |
266 | Injecting your own RestClient!
267 | --------------------------------
268 |
269 | Yup, your read right, you can even inject your own RestClient implementation thru the builder.
270 |
271 | // you can override any of the RestClient methods you desire
272 |
273 | public class MyRestClient extends RestClient {
274 | protected MyRestClient(RestClientBuilder builder) {
275 | super(builder);
276 | }
277 |
278 | @Override
279 | protected HttpGet newHttpGet(String url) {
280 | ...
281 | }
282 | }
283 |
284 | RestClient client = RestClient.builder().restClientClass(MyRestClient.class).build();
285 |
286 | Look at `test.integration.CustomRestClientTest` to a see working example.
287 |
288 |
289 | Next Steps
290 | ------------------------------
291 |
292 | Add more tests & a plugin to get code coverage.
293 |
294 | Currently there are 9 tests, 5 integration, and 4 unit.
295 |
296 |
297 | Cheers!
298 |
299 |
300 |
301 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import bintray.Keys._
2 |
3 | name := "http-rest-client"
4 |
5 | organization := "g00dnatur3"
6 |
7 | version := "1.0.21"
8 |
9 | bintrayPublishSettings
10 |
11 | repository in bintray := "java-utils"
12 |
13 | licenses += ("Apache-2.0", url("http://www.apache.org/licenses/LICENSE-2.0.html"))
14 |
15 | bintrayOrganization in bintray := None
16 |
17 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
18 |
19 | libraryDependencies ++= Seq(
20 | "org.apache.httpcomponents" % "httpclient" % "4.3.4",
21 | "commons-io" % "commons-io" % "2.4",
22 | "org.slf4j" % "slf4j-api" % "1.7.7",
23 | "org.slf4j" % "slf4j-simple" % "1.7.7",
24 | "com.fasterxml.jackson.core" % "jackson-databind" % "2.4.1.2",
25 | "com.google.guava" % "guava" % "17.0" % "test",
26 | "com.novocode" % "junit-interface" % "0.9" % "test",
27 | "org.mockito" % "mockito-all" % "1.9.5" % "test"
28 | )
29 |
30 |
--------------------------------------------------------------------------------
/project/bintray.sbt:
--------------------------------------------------------------------------------
1 | resolvers += Resolver.url(
2 | "bintray-sbt-plugin-releases",
3 | url("http://dl.bintray.com/content/sbt/sbt-plugin-releases"))(
4 | Resolver.ivyStylePatterns)
5 |
6 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2")
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.0
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1")
2 |
3 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
4 |
--------------------------------------------------------------------------------
/src/main/java/http/rest/AbstractRestClient.java:
--------------------------------------------------------------------------------
1 | package http.rest;
2 |
3 | import java.io.IOException;
4 | import java.io.UnsupportedEncodingException;
5 | import java.net.URLEncoder;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import org.apache.commons.io.Charsets;
10 | import org.apache.commons.io.IOUtils;
11 | import org.apache.http.HttpResponse;
12 | import org.apache.http.client.ClientProtocolException;
13 | import org.apache.http.client.HttpClient;
14 | import org.apache.http.client.methods.HttpDelete;
15 | import org.apache.http.client.methods.HttpGet;
16 | import org.apache.http.client.methods.HttpPost;
17 | import org.apache.http.client.methods.HttpPut;
18 | import org.apache.http.client.methods.HttpRequestBase;
19 | import org.apache.http.client.methods.HttpUriRequest;
20 | import org.apache.http.entity.ContentType;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | import com.fasterxml.jackson.core.type.TypeReference;
25 | import com.fasterxml.jackson.databind.JsonNode;
26 | import com.fasterxml.jackson.databind.ObjectMapper;
27 |
28 | public abstract class AbstractRestClient {
29 |
30 | protected final Logger logger = LoggerFactory.getLogger(getClass());
31 |
32 | protected final HttpClient client;
33 |
34 | protected final ObjectMapper mapper;
35 |
36 | protected final RequestInterceptor interceptor;
37 |
38 | protected AbstractRestClient(RestClientBuilder builder) {
39 | this.client = builder.client;
40 | this.mapper = builder.mapper;
41 | this.interceptor = builder.interceptor;
42 | }
43 |
44 | public static RestClientBuilder builder() {
45 | return new RestClientBuilder();
46 | };
47 |
48 | protected T bindObject(String source, Class entityClass) throws IOException {
49 | return mapper.readValue(source, entityClass);
50 | }
51 |
52 | protected List bindJsonArray(String source, TypeReference> type) throws IOException {
53 | return mapper.readValue(source, type);
54 | }
55 |
56 | protected JsonNode toJson(Object object) throws IOException {
57 | if (object instanceof JsonNode)
58 | return (JsonNode) object;
59 | return mapper.valueToTree(object);
60 | }
61 |
62 | protected JsonNode toJsonArray(List data) throws IOException {
63 | return mapper.convertValue(data, JsonNode.class);
64 | }
65 |
66 | protected byte[] contentAsBytes(HttpResponse response) throws IOException {
67 | return IOUtils.toByteArray(response.getEntity().getContent());
68 | }
69 |
70 | protected HttpResponse execute(RequestInterceptor interceptor, HttpRequestBase request)
71 | throws ClientProtocolException, IOException {
72 |
73 | if (interceptor != null) {
74 | interceptor.intercept(request);
75 | } else if (this.interceptor != null) {
76 | this.interceptor.intercept(request);
77 | }
78 | return client.execute(request);
79 | }
80 |
81 | protected HttpResponse execute(RequestInterceptor interceptor, HttpRequestBase request, int expectedStatus)
82 | throws RestClientException {
83 |
84 | String method = request.getMethod();
85 | String path = request.getURI().toString();
86 | logger.info("Send -> " + method + " " + path);
87 | HttpResponse response = null;
88 | try {
89 | response = execute(interceptor, request);
90 | } catch (Exception e) {
91 | throw new RestClientException(e, response);
92 | }
93 | int status = response.getStatusLine().getStatusCode();
94 | if (status >= 200 && status < 400) {
95 | logger.info("[" + status + "] Successfully sent " + method + " " + path);
96 | } else {
97 | logger.error("[" + status + "] Failed to send " + method + " " + path);
98 | }
99 | if (expectedStatus != status) {
100 | StringBuilder sb = new StringBuilder("Status of " + status);
101 | sb.append(" not equal to expected value of ").append(expectedStatus);
102 | throw new RestClientException(sb.toString(), response);
103 | }
104 | return response;
105 | }
106 |
107 | protected int successStatus(String method) {
108 | if (method.equalsIgnoreCase("POST")) {
109 | return 201;
110 | }
111 | return 200;
112 | }
113 |
114 | protected String appendParams(String path, Map params) {
115 | if (params != null) {
116 | return path + queryString(params);
117 | }
118 | return path;
119 | }
120 |
121 | protected String queryString(Map params) {
122 | if (params == null || params.isEmpty())
123 | return "";
124 | StringBuilder sb = new StringBuilder("?");
125 | int i = 0;
126 | for (Map.Entry entry : params.entrySet()) {
127 | String key = entry.getKey();
128 | String value = entry.getValue();
129 | if (i > 0) {
130 | sb.append("&");
131 | }
132 | try {
133 | sb.append(key).append("=").append(URLEncoder.encode(value, Charsets.UTF_8.toString()));
134 | } catch (UnsupportedEncodingException e) {
135 | e.printStackTrace();
136 | }
137 | i++;
138 | }
139 | return sb.toString();
140 | }
141 |
142 | protected T contentTypeJson(T request) {
143 | request.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
144 | return request;
145 | }
146 |
147 | protected HttpPost newHttpPost(String url) {
148 | return new HttpPost(url);
149 | }
150 |
151 | protected HttpGet newHttpGet(String url) {
152 | return new HttpGet(url);
153 | }
154 |
155 | protected HttpPut newHttpPut(String url) {
156 | return new HttpPut(url);
157 | }
158 |
159 | protected HttpDelete newHttpDelete(String url) {
160 | return new HttpDelete(url);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/java/http/rest/RequestInterceptor.java:
--------------------------------------------------------------------------------
1 | package http.rest;
2 |
3 | import org.apache.http.client.methods.HttpRequestBase;
4 |
5 | public class RequestInterceptor {
6 |
7 | public void intercept(HttpRequestBase request) {
8 | }
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/http/rest/RestClient.java:
--------------------------------------------------------------------------------
1 | package http.rest;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | import org.apache.commons.io.Charsets;
8 | import org.apache.commons.io.IOUtils;
9 | import org.apache.http.Header;
10 | import org.apache.http.HttpEntity;
11 | import org.apache.http.HttpResponse;
12 | import org.apache.http.client.methods.HttpDelete;
13 | import org.apache.http.client.methods.HttpGet;
14 | import org.apache.http.client.methods.HttpPost;
15 | import org.apache.http.client.methods.HttpPut;
16 | import org.apache.http.entity.StringEntity;
17 | import org.apache.http.util.EntityUtils;
18 |
19 | import com.fasterxml.jackson.core.type.TypeReference;
20 |
21 | public class RestClient extends AbstractRestClient {
22 |
23 | protected RestClient(RestClientBuilder builder) {
24 | super(builder);
25 | }
26 |
27 | protected String get(RequestInterceptor interceptor, String path, Map queryParams,
28 | int expectedStatus) throws RestClientException, IOException {
29 |
30 | HttpGet get = newHttpGet(appendParams(path, queryParams));
31 | HttpResponse response = execute(interceptor, get, expectedStatus);
32 | String content = null;
33 | try {
34 | content = contentAsString(response);
35 | } catch (IOException e) {
36 | consume(response);
37 | }
38 | return content;
39 | }
40 |
41 | public T get(RequestInterceptor interceptor, String path, Map queryParams,
42 | Class entityClass, int expectedStatus) throws RestClientException, IOException {
43 | String content = get(interceptor, path, queryParams, expectedStatus);
44 | if (content != null) {
45 | return bindObject(content, entityClass);
46 | } else {
47 | return null;
48 | }
49 | }
50 |
51 | public List get(RequestInterceptor interceptor, String path, Map params,
52 | TypeReference> type, int expectedStatus) throws RestClientException, IOException {
53 | String content = get(interceptor, path, params, expectedStatus);
54 | if (content != null) {
55 | return bindJsonArray(content, type);
56 | } else {
57 | return null;
58 | }
59 | }
60 |
61 | public T get(RequestInterceptor interceptor, String path, Map params, Class entityClass)
62 | throws RestClientException, IOException {
63 | return get(interceptor, path, params, entityClass, 200);
64 | }
65 |
66 | public T get(String path, Map params, Class entityClass, int expectedStatus)
67 | throws RestClientException, IOException {
68 | return get(null, path, params, entityClass, expectedStatus);
69 | }
70 |
71 | public T get(String path, Map params, Class entityClass) throws RestClientException,
72 | IOException {
73 | return get(null, path, params, entityClass, 200);
74 | }
75 |
76 | public List get(RequestInterceptor interceptor, String path, Map params,
77 | TypeReference> type) throws RestClientException, IOException {
78 | return get(interceptor, path, params, type, 200);
79 | }
80 |
81 | public List get(String path, Map params, TypeReference> type, int expectedStatus)
82 | throws RestClientException, IOException {
83 | return get(null, path, params, type, expectedStatus);
84 | }
85 |
86 | public List get(String path, Map params, TypeReference> type)
87 | throws RestClientException, IOException {
88 | return get(null, path, params, type, 200);
89 | }
90 |
91 | public Header create(RequestInterceptor interceptor, String path, Object object, int expectedStatus)
92 | throws RestClientException, IOException {
93 | HttpPost post = contentTypeJson(newHttpPost(path));
94 | HttpEntity entity = new StringEntity(toJson(object).toString(), Charsets.UTF_8);
95 | post.setEntity(entity);
96 | HttpResponse response = execute(interceptor, post, expectedStatus);
97 | consume(response);
98 | return response.getFirstHeader("Location");
99 | }
100 |
101 | public Header create(String path, Object object, int expectedStatus) throws RestClientException, IOException {
102 | return create(null, path, object, expectedStatus);
103 | }
104 |
105 | public Header create(RequestInterceptor interceptor, String path, Object object) throws RestClientException,
106 | IOException {
107 | return create(interceptor, path, object, 201);
108 | }
109 |
110 | public Header create(String path, Object object) throws RestClientException, IOException {
111 | return create(null, path, object, 201);
112 | }
113 |
114 | public Header create(RequestInterceptor interceptor, String path, List> data, int expectedStatus)
115 | throws RestClientException, IOException {
116 | HttpPost post = contentTypeJson(newHttpPost(path));
117 | HttpEntity entity = new StringEntity(toJsonArray(data).toString(), Charsets.UTF_8);
118 | post.setEntity(entity);
119 | HttpResponse response = execute(interceptor, post, expectedStatus);
120 | consume(response);
121 | return response.getFirstHeader("Location");
122 | }
123 |
124 | public Header create(String path, List> data, int expectedStatus) throws RestClientException, IOException {
125 | return create(null, path, data, expectedStatus);
126 | }
127 |
128 | public Header create(RequestInterceptor interceptor, String path, List> data) throws RestClientException,
129 | IOException {
130 | return create(interceptor, path, data, 201);
131 | }
132 |
133 | public Header create(String path, List> data) throws RestClientException, IOException {
134 | return create(null, path, data, 201);
135 | }
136 |
137 | public void delete(RequestInterceptor interceptor, String path, int expectedStatus) throws RestClientException,
138 | IOException {
139 | HttpDelete delete = newHttpDelete(path);
140 | consume(execute(interceptor, delete, expectedStatus));
141 | }
142 |
143 | public void delete(RequestInterceptor interceptor, String path) throws RestClientException, IOException {
144 | delete(interceptor, path, 200);
145 | }
146 |
147 | public void delete(String path, int expectedStatus) throws RestClientException, IOException {
148 | delete(null, path, expectedStatus);
149 | }
150 |
151 | public void delete(String path) throws RestClientException, IOException {
152 | delete(null, path, 200);
153 | }
154 |
155 | public void update(RequestInterceptor interceptor, String path, Object object, int expectedStatus)
156 | throws RestClientException, IOException {
157 |
158 | HttpPut put = contentTypeJson(newHttpPut(path));
159 | HttpEntity entity = new StringEntity(toJson(object).toString(), Charsets.UTF_8);
160 | put.setEntity(entity);
161 | consume(execute(interceptor, put, expectedStatus));
162 | }
163 |
164 | public void update(RequestInterceptor interceptor, String path, Object object) throws RestClientException,
165 | IOException {
166 | update(interceptor, path, object, 200);
167 | }
168 |
169 | public void update(String path, Object object) throws RestClientException, IOException {
170 | update(null, path, object, 200);
171 | }
172 |
173 | public void update(String path, Object object, int expectedStatus) throws RestClientException, IOException {
174 | update(null, path, object, expectedStatus);
175 | }
176 |
177 | public void update(RequestInterceptor interceptor, String path, List> data, int expectedStatus)
178 | throws RestClientException, IOException {
179 |
180 | HttpPut put = contentTypeJson(newHttpPut(path));
181 | HttpEntity entity = new StringEntity(toJsonArray(data).toString(), Charsets.UTF_8);
182 | put.setEntity(entity);
183 | consume(execute(interceptor, put, expectedStatus));
184 | }
185 |
186 | public void update(RequestInterceptor interceptor, String path, List> data) throws RestClientException,
187 | IOException {
188 | update(interceptor, path, data, 200);
189 | }
190 |
191 | public void update(String path, List> data) throws RestClientException, IOException {
192 | update(null, path, data, 200);
193 | }
194 |
195 | public void update(String path, List> data, int expectedStatus) throws RestClientException, IOException {
196 | update(null, path, data, expectedStatus);
197 | }
198 |
199 | public void consume(HttpResponse response) throws IOException {
200 | if (response != null && response.getEntity() != null) {
201 | EntityUtils.consume(response.getEntity());
202 | }
203 | }
204 |
205 | public String contentAsString(HttpResponse response) throws IOException {
206 | if (response != null && response.getEntity() != null) {
207 | return IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
208 | }
209 | return null;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/main/java/http/rest/RestClientBuilder.java:
--------------------------------------------------------------------------------
1 | package http.rest;
2 |
3 | import java.lang.reflect.Constructor;
4 |
5 | import org.apache.http.client.HttpClient;
6 | import org.apache.http.impl.client.HttpClientBuilder;
7 |
8 | import com.fasterxml.jackson.annotation.JsonInclude.Include;
9 | import com.fasterxml.jackson.databind.ObjectMapper;
10 |
11 | public class RestClientBuilder {
12 |
13 | protected HttpClient client;
14 |
15 | protected ObjectMapper mapper;
16 |
17 | protected RequestInterceptor interceptor;
18 |
19 | protected Class extends RestClient> clazz;
20 |
21 | protected RestClientBuilder() {
22 | }
23 |
24 | public RestClientBuilder httpClient(HttpClient client) {
25 | this.client = client;
26 | return this;
27 | }
28 |
29 | public RestClientBuilder objectMapper(ObjectMapper mapper) {
30 | this.mapper = mapper;
31 | return this;
32 | }
33 |
34 | public RestClientBuilder requestInterceptor(RequestInterceptor interceptor) {
35 | this.interceptor = interceptor;
36 | return this;
37 | }
38 |
39 | public RestClientBuilder restClientClass(Class extends RestClient> clazz) {
40 | this.clazz = clazz;
41 | return this;
42 | }
43 |
44 | public RestClient build() {
45 | if (clazz == null) {
46 | clazz = RestClient.class;
47 | }
48 | if (mapper == null) {
49 | mapper = new ObjectMapper();
50 | mapper.setSerializationInclusion(Include.NON_NULL);
51 | }
52 | if (client == null) {
53 | client = HttpClientBuilder.create().useSystemProperties().build();
54 | }
55 | return createRestClient(this, clazz);
56 | }
57 |
58 | protected T createRestClient(RestClientBuilder builder, Class restClientClass) {
59 | try {
60 | Constructor constructor = restClientClass.getDeclaredConstructor(RestClientBuilder.class);
61 | constructor.setAccessible(true);
62 | return constructor.newInstance(builder);
63 | } catch (Exception e) {
64 | throw new RuntimeException(e);
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/http/rest/RestClientException.java:
--------------------------------------------------------------------------------
1 | package http.rest;
2 |
3 | import org.apache.http.HttpResponse;
4 |
5 | public class RestClientException extends Exception {
6 | private static final long serialVersionUID = -785745983831601722L;
7 |
8 | private final HttpResponse response;
9 |
10 | public RestClientException(String message, HttpResponse response) {
11 | super(message);
12 | this.response = response;
13 | }
14 |
15 | public RestClientException(Exception e, HttpResponse response) {
16 | super(e);
17 | this.response = response;
18 | }
19 |
20 | public HttpResponse response() {
21 | return response;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/test/java/test/Assertions.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import static org.junit.Assert.assertNotNull;
4 |
5 | import com.fasterxml.jackson.databind.JsonNode;
6 |
7 | public class Assertions {
8 |
9 | public static void hasAddressComponents(JsonNode node) {
10 | assertNotNull(node);
11 | assertNotNull(node.get("results"));
12 | assertNotNull(node.get("results").get(0));
13 | assertNotNull(node.get("results").get(0).get("address_components"));
14 | assertNotNull(node.get("results").get(0).get("address_components").get(0));
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/test/Settings.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import java.io.IOException;
4 |
5 | import org.apache.commons.io.IOUtils;
6 |
7 | public class Settings {
8 |
9 | public static final String geocoderUrl = "http://maps.googleapis.com/maps/api/geocode/json";
10 |
11 | public static final String mockLocationHeader = "http://mock.org/created/1";
12 |
13 | public static final String geocodeJson;
14 |
15 | static {
16 | geocodeJson = loadMockJson("/geocode.json");
17 | }
18 |
19 | public static String loadMockJson(String path) {
20 | try {
21 | return IOUtils.toString(Settings.class.getResourceAsStream(path));
22 | } catch (IOException e) {
23 | throw new RuntimeException(e);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/test/integration/CustomRestClientTest.java:
--------------------------------------------------------------------------------
1 | package test.integration;
2 |
3 | import http.rest.RestClient;
4 | import http.rest.RestClientBuilder;
5 |
6 | import java.util.Map;
7 |
8 | import org.apache.http.client.methods.HttpGet;
9 | import org.junit.Test;
10 |
11 | import test.Assertions;
12 | import test.Settings;
13 |
14 | import com.fasterxml.jackson.databind.JsonNode;
15 | import com.google.common.collect.Maps;
16 |
17 | public class CustomRestClientTest {
18 |
19 | RestClient client;
20 |
21 | @Test
22 | public void injectMyRestClient() throws Exception {
23 | client = RestClient.builder().restClientClass(MyRestClient.class).build();
24 | JsonNode node = client.get(null, null, JsonNode.class);
25 | Assertions.hasAddressComponents(node);
26 | }
27 |
28 | public static class MyRestClient extends RestClient {
29 | protected MyRestClient(RestClientBuilder builder) {
30 | super(builder);
31 | }
32 |
33 | @Override
34 | protected HttpGet newHttpGet(String url) {
35 | Map params = Maps.newHashMap();
36 | params.put("address", "1980 W. Bayshore Rd. 94303");
37 | params.put("sensor", "false");
38 | url = appendParams(Settings.geocoderUrl, params);
39 | return new HttpGet(url);
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/test/integration/GoogleGeocodeTest.java:
--------------------------------------------------------------------------------
1 | package test.integration;
2 |
3 | import http.rest.RestClient;
4 |
5 | import java.net.URLEncoder;
6 | import java.util.Map;
7 |
8 | import org.junit.Before;
9 | import org.junit.Test;
10 |
11 | import test.Assertions;
12 | import test.Settings;
13 |
14 | import com.fasterxml.jackson.databind.JsonNode;
15 | import com.google.common.base.Charsets;
16 | import com.google.common.collect.Maps;
17 |
18 | public class GoogleGeocodeTest {
19 |
20 | RestClient client;
21 |
22 | @Before
23 | public void before() {
24 | client = RestClient.builder().build();
25 | }
26 |
27 | @Test
28 | public void getGeocodeWithParamsAsMap() throws Exception {
29 | Map params = Maps.newHashMap();
30 | params.put("address", "1980 W. Bayshore Rd. 94303");
31 | params.put("sensor", "false");
32 | doAssertions(client.get(Settings.geocoderUrl, params, JsonNode.class));
33 | }
34 |
35 | @Test
36 | public void getGeocodeWithParamsAsString() throws Exception {
37 | String address = "1980 W. Bayshore Rd. 94303";
38 | String url = Settings.geocoderUrl + "?address=" + URLEncoder.encode(address, Charsets.UTF_8.toString())
39 | + "&sensor=false";
40 | doAssertions(client.get(url, null, JsonNode.class));
41 | }
42 |
43 | private void doAssertions(JsonNode node) {
44 | Assertions.hasAddressComponents(node);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/test/integration/RequestInterceptorTest.java:
--------------------------------------------------------------------------------
1 | package test.integration;
2 |
3 | import http.rest.RequestInterceptor;
4 | import http.rest.RestClient;
5 |
6 | import java.net.URLEncoder;
7 |
8 | import org.apache.commons.io.Charsets;
9 | import org.apache.http.client.methods.HttpRequestBase;
10 | import org.apache.http.client.methods.HttpUriRequest;
11 | import org.apache.http.params.BasicHttpParams;
12 | import org.apache.http.params.HttpParams;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 |
16 | import test.Assertions;
17 | import test.Settings;
18 |
19 | import com.fasterxml.jackson.databind.JsonNode;
20 |
21 | public class RequestInterceptorTest {
22 |
23 | RestClient client;
24 |
25 | @Before
26 | public void before() {
27 | client = RestClient.builder().build();
28 | }
29 |
30 | @Test
31 | public void interceptAllRequestsThruTheClientBuilder() throws Exception {
32 | final String address = "1980 W. Bayshore Rd. 94303";
33 | final String url = Settings.geocoderUrl + "?address=" + URLEncoder.encode(address, Charsets.UTF_8.toString())
34 | + "&sensor=false";
35 |
36 | client = RestClient.builder().requestInterceptor(new RequestInterceptor() {
37 | @Override
38 | public void intercept(HttpRequestBase request) {
39 | setParams(request, address, false);
40 | }
41 | }).build();
42 | doAssertions(client.get(url, null, JsonNode.class));
43 | }
44 |
45 | @Test
46 | public void interceptReqeustsAsNeeded() throws Exception {
47 | final String address = "1980 W. Bayshore Rd. 94303";
48 | final String url = Settings.geocoderUrl + "?address=" + URLEncoder.encode(address, Charsets.UTF_8.toString())
49 | + "&sensor=false";
50 |
51 | doAssertions(client.get(new RequestInterceptor() {
52 | @Override
53 | public void intercept(HttpRequestBase request) {
54 | setParams(request, address, false);
55 | }
56 | }, url, null, JsonNode.class));
57 | }
58 |
59 | private void setParams(HttpUriRequest request, String address, boolean sensor) {
60 | // i don't recommend to set parameters this way,
61 | // its only here so i can verify the interceptor is working
62 | HttpParams params = new BasicHttpParams();
63 | params.setParameter("address", address);
64 | params.setBooleanParameter("sensor", false);
65 | request.setParams(params);
66 | }
67 |
68 | private void doAssertions(JsonNode node) {
69 | Assertions.hasAddressComponents(node);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/test/unit/BaseTest.java:
--------------------------------------------------------------------------------
1 | package test.unit;
2 |
3 | import static org.mockito.Mockito.mock;
4 | import static org.mockito.Mockito.when;
5 | import http.rest.RequestInterceptor;
6 | import http.rest.RestClient;
7 | import http.rest.RestClientBuilder;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.commons.io.IOUtils;
12 | import org.apache.http.Header;
13 | import org.apache.http.HttpEntity;
14 | import org.apache.http.HttpResponse;
15 | import org.apache.http.StatusLine;
16 | import org.apache.http.client.ClientProtocolException;
17 | import org.apache.http.client.methods.HttpGet;
18 | import org.apache.http.client.methods.HttpPost;
19 | import org.apache.http.client.methods.HttpRequestBase;
20 | import org.apache.http.client.methods.HttpUriRequest;
21 |
22 | import test.Settings;
23 |
24 | public class BaseTest {
25 |
26 | RestClient client;
27 |
28 | public static class MockRestClient extends RestClient {
29 |
30 | protected MockRestClient(RestClientBuilder builder) {
31 | super(builder);
32 | }
33 |
34 | @Override
35 | protected HttpResponse execute(RequestInterceptor interceptor, HttpRequestBase request)
36 | throws ClientProtocolException, IOException {
37 |
38 | return mockHttpResponse(request);
39 | }
40 | }
41 |
42 | public static HttpResponse mockHttpResponse(HttpUriRequest request) throws IOException {
43 | HttpResponse mockHttpResponse = mock(HttpResponse.class);
44 | StatusLine statusLine = mock(StatusLine.class);
45 | if (request instanceof HttpPost) {
46 | when(statusLine.getStatusCode()).thenReturn(201);
47 | when(mockHttpResponse.getStatusLine()).thenReturn(statusLine);
48 | Header header = mock(Header.class);
49 | when(header.getValue()).thenReturn(Settings.mockLocationHeader);
50 | when(mockHttpResponse.getFirstHeader("Location")).thenReturn(header);
51 | } else {
52 | when(statusLine.getStatusCode()).thenReturn(200);
53 | when(mockHttpResponse.getStatusLine()).thenReturn(statusLine);
54 | }
55 | if (request instanceof HttpGet) {
56 | HttpEntity entity = mock(HttpEntity.class);
57 | when(entity.getContent()).thenReturn(IOUtils.toInputStream(Settings.geocodeJson));
58 | when(mockHttpResponse.getEntity()).thenReturn(entity);
59 | }
60 | return mockHttpResponse;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/test/unit/TestRestClient.java:
--------------------------------------------------------------------------------
1 | package test.unit;
2 |
3 | import junit.framework.Assert;
4 | import http.rest.RestClient;
5 |
6 | import org.apache.http.Header;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import test.Assertions;
11 | import test.Settings;
12 |
13 | import com.fasterxml.jackson.databind.JsonNode;
14 | import com.fasterxml.jackson.databind.node.JsonNodeFactory;
15 | import com.fasterxml.jackson.databind.node.ObjectNode;
16 |
17 | public class TestRestClient extends BaseTest {
18 |
19 | RestClient client;
20 |
21 | @Before
22 | public void before() throws Exception {
23 | client = RestClient.builder().restClientClass(MockRestClient.class).build();
24 | }
25 |
26 | @Test
27 | public void get() throws Exception {
28 | JsonNode node = client.get(Settings.geocoderUrl, null, JsonNode.class);
29 | Assertions.hasAddressComponents(node);
30 | }
31 |
32 | @Test
33 | public void post() throws Exception {
34 | Header header = client.create(Settings.geocoderUrl, new ObjectNode(JsonNodeFactory.instance));
35 | Assert.assertEquals(Settings.mockLocationHeader, header.getValue());
36 | }
37 |
38 | @Test
39 | public void put() throws Exception {
40 | client.update(Settings.geocoderUrl, new ObjectNode(JsonNodeFactory.instance));
41 | }
42 |
43 | @Test
44 | public void delete() throws Exception {
45 | client.delete(Settings.geocoderUrl);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/resources/geocode.json:
--------------------------------------------------------------------------------
1 | {"results":[{"address_components":[{"long_name":"1980","short_name":"1980","types":["street_number"]},{"long_name":"West Bayshore Road","short_name":"W Bayshore Rd","types":["route"]},{"long_name":"East Palo Alto","short_name":"East Palo Alto","types":["locality","political"]},{"long_name":"California","short_name":"CA","types":["administrative_area_level_1","political"]},{"long_name":"United States","short_name":"US","types":["country","political"]},{"long_name":"94303","short_name":"94303","types":["postal_code"]}],"formatted_address":"1980 West Bayshore Road, East Palo Alto, CA 94303, USA","geometry":{"bounds":{"northeast":{"lat":37.4528755,"lng":-122.1285317},"southwest":{"lat":37.4528638,"lng":-122.1285426}},"location":{"lat":37.4528638,"lng":-122.1285426},"location_type":"RANGE_INTERPOLATED","viewport":{"northeast":{"lat":37.45421863029151,"lng":-122.1271881697085},"southwest":{"lat":37.45152066970851,"lng":-122.1298861302915}}},"types":["street_address"]}],"status":"OK"}
2 |
--------------------------------------------------------------------------------