This is an automated email from the ASF dual-hosted git repository. jamesbognar pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push: new ef35e32 RestClient tests. ef35e32 is described below commit ef35e32afedbfd9cbb2db2903823fb81da7431ce Author: JamesBognar <james.bog...@salesforce.com> AuthorDate: Wed Jun 10 17:53:57 2020 -0400 RestClient tests. --- .../apache/juneau/internal/StateMachineState.java | 13 + .../org/apache/juneau/rest/test/_TestSuite.java | 1 - .../juneau/rest/client2}/CallbackStringsTest.java | 17 +- .../apache/juneau/rest/client2/RestClientTest.java | 457 ++++++++++++++++++--- .../org/apache/juneau/rest/client2/RestClient.java | 175 ++++---- .../juneau/rest/client2/RestClientBuilder.java | 91 ++++ .../apache/juneau/rest/client2/RestRequest.java | 136 +++++- .../apache/juneau/rest/mock2/MockRestClient.java | 1 + .../juneau/rest/mock2/MockRestClientBuilder.java | 18 + 9 files changed, 760 insertions(+), 149 deletions(-) diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StateMachineState.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StateMachineState.java index 9232bff..755a0ef 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StateMachineState.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StateMachineState.java @@ -18,4 +18,17 @@ package org.apache.juneau.internal; @SuppressWarnings("javadoc") public enum StateMachineState { S01, S02, S03, S04, S05, S06, S07, S08, S09, S10, S11, S12, S13, S14, S15, S16, S17, S18, S19, S20; + + /** + * Returns <jk>true</jk> if the state is any one of the specified states. + * + * @param states The states to check. + * @return <jk>true</jk> if the state is any one of the specified states. + */ + public boolean isAny(StateMachineState...states) { + for (StateMachineState s : states) + if (this == s) + return true; + return false; + } } diff --git a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/_TestSuite.java b/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/_TestSuite.java index e1da651..f3ad721 100644 --- a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/_TestSuite.java +++ b/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/_TestSuite.java @@ -25,7 +25,6 @@ import org.junit.runners.Suite.*; */ @RunWith(Suite.class) @SuiteClasses({ - CallbackStringsTest.class, ClientFuturesTest.class, ConfigTest.class, FormDataTest.class, diff --git a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/client/CallbackStringsTest.java b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/CallbackStringsTest.java similarity index 88% rename from juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/client/CallbackStringsTest.java rename to juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/CallbackStringsTest.java index 4a520fe..6c43f28 100644 --- a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/client/CallbackStringsTest.java +++ b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/CallbackStringsTest.java @@ -10,7 +10,7 @@ // * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * // * specific language governing permissions and limitations under the License. * // *************************************************************************************************************************** -package org.apache.juneau.rest.test.client; +package org.apache.juneau.rest.client2; import static org.apache.juneau.http.HttpMethodName.*; import static org.junit.Assert.*; @@ -21,7 +21,6 @@ import java.util.*; import org.apache.juneau.collections.*; import org.apache.juneau.rest.RestRequest; import org.apache.juneau.rest.annotation.*; -import org.apache.juneau.rest.client2.*; import org.apache.juneau.rest.mock2.*; import org.junit.*; @@ -53,7 +52,7 @@ public class CallbackStringsTest { static RestClient a = MockRestClient.build(A.class); @Test - public void a01() throws Exception { + public void a01_basicTests() throws Exception { String r; r = a.callback("GET /testCallback").run().getBody().asString(); @@ -74,4 +73,16 @@ public class CallbackStringsTest { r = a.callback("PUT {Foo-X:123,Foo-Y:'abc'} /testCallback some sample content ").run().getBody().asString(); assertEquals("{method:'PUT',headers:{'Foo-X':'123','Foo-Y':'abc'},content:'some sample content'}", r); } + + @Test + public void a02_invalidStrings() throws Exception { + for (String s : AList.of("", "GET", "GET ", "GET {", "GET {xxx} /foo", null)) { + try { + a.callback(s).run().getBody().asString(); + fail("'"+s+"' didn't fail"); + } catch (RestCallException e) { + assertTrue(e.getMessage().startsWith("Invalid format for call string.")); + } + } + } } diff --git a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientTest.java b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientTest.java index d58b342..1e12e17 100644 --- a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientTest.java +++ b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientTest.java @@ -232,20 +232,33 @@ public class RestClientTest { } @Test - public void a06_basicCalls_put() throws Exception { - MockRestClient + public void a06_basicCalls_get_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient .create(A.class) .simpleJson() .build() - .put("/bean", bean) + .get(url) .run() .assertBody().is("{f:1}"); + } + } + @Test + public void a07_basicCalls_put() throws Exception { MockRestClient .create(A.class) .simpleJson() .build() - .put("/bean", "{f:1}", "application/json") + .put("/bean", bean) .run() .assertBody().is("{f:1}"); @@ -257,9 +270,30 @@ public class RestClientTest { .body(bean) .run() .assertBody().is("{f:1}"); + } - // Different URL types. - for (Object url : AList.<Object>of(new URIBuilder("http://localhost/bean"), java.net.URI.create("http://localhost/bean"), new URL("http://localhost/bean"), "/bean", new StringBuilder("/bean"))) { + @Test + public void a08_basicCalls_put_fromString() throws Exception { + MockRestClient + .create(A.class) + .simpleJson() + .build() + .put("/bean", "{f:1}", "application/json") + .run() + .assertBody().is("{f:1}"); + } + + @Test + public void a09_basicCalls_put_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { MockRestClient .create(A.class) .simpleJson() @@ -285,9 +319,11 @@ public class RestClientTest { .run() .assertBody().is("{f:1}"); } + } - // Different body types. - List<Object> l = AList.<Object>of( + @Test + public void a10_basicCalls_put_exhaustiveBodyTypes() throws Exception { + List<Object> bodies = AList.<Object>of( new StringReader("{f:1}"), new ByteArrayInputStream("{f:1}".getBytes()), ReaderResource.create().contents("{f:1}").build(), @@ -296,7 +332,8 @@ public class RestClientTest { new StringEntity("{f:1}"), NameValuePairs.of("f", 1) ); - for (Object body : l) { + + for (Object body : bodies) { MockRestClient .create(A.class) .simpleJson() @@ -309,7 +346,7 @@ public class RestClientTest { } @Test - public void a07_basicCalls_post() throws Exception { + public void a11_basicCalls_post() throws Exception { MockRestClient .create(A.class) .simpleJson() @@ -322,136 +359,391 @@ public class RestClientTest { .create(A.class) .simpleJson() .build() - .post("/bean", "{f:1}", "application/json") + .post("/bean") + .body(bean) .run() .assertBody().is("{f:1}"); + } + @Test + public void a12_basicCalls_post_fromString() throws Exception { MockRestClient .create(A.class) .simpleJson() .build() - .post("/bean") - .body(bean) + .post("/bean", "{f:1}", "application/json") .run() .assertBody().is("{f:1}"); } @Test - public void a08_basicCalls_delete() throws Exception { - MockRestClient - .create(A.class) - .simpleJson() - .build() - .delete("/bean") - .run() - .assertBody().is("{f:1}"); + public void a13_basicCalls_post_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .simpleJson() + .build() + .post(url, bean) + .run() + .assertBody().is("{f:1}"); + + MockRestClient + .create(A.class) + .simpleJson() + .build() + .post(url, "{f:1}", "application/json") + .run() + .assertBody().is("{f:1}"); + + MockRestClient + .create(A.class) + .simpleJson() + .build() + .post(url) + .body(bean) + .run() + .assertBody().is("{f:1}"); + } } @Test - public void a09_basicCalls_options() throws Exception { - MockRestClient - .create(A.class) - .simpleJson() - .build() - .options("/bean") - .run() - .assertBody().is("{f:1}"); + public void a14_basicCalls_post_exhaustiveBodyTypes() throws Exception { + List<Object> bodies = AList.<Object>of( + new StringReader("{f:1}"), + new ByteArrayInputStream("{f:1}".getBytes()), + ReaderResource.create().contents("{f:1}").build(), + StreamResource.create().contents("{f:1}").build(), + bean, + new StringEntity("{f:1}"), + NameValuePairs.of("f", 1) + ); + + for (Object body : bodies) { + MockRestClient + .create(A.class) + .simpleJson() + .contentType(body instanceof NameValuePairs ? "application/x-www-form-urlencoded" : "application/json") + .build() + .post("/bean", body) + .run() + .assertBody().is("{f:1}"); + } } @Test - public void a10_basicCalls_head() throws Exception { + public void a15_basicCalls_delete() throws Exception { MockRestClient .create(A.class) .simpleJson() .build() - .head("/bean") + .delete("/bean") .run() - .assertBody().is(""); + .assertBody().is("{f:1}"); } @Test - public void a11_basicCalls_formPost() throws Exception { + public void a16_basicCalls_delete_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .simpleJson() + .build() + .delete(url) + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a17_basicCalls_options() throws Exception { MockRestClient .create(A.class) + .simpleJson() .build() - .formPost("/bean", bean) - .accept("application/json+simple") + .options("/bean") .run() .assertBody().is("{f:1}"); + } + @Test + public void a18_basicCalls_options_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .simpleJson() + .build() + .options(url) + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a19_basicCalls_head() throws Exception { MockRestClient .create(A.class) + .simpleJson() .build() - .formPost("/bean") - .body(bean) - .accept("application/json+simple") + .head("/bean") .run() - .assertBody().is("{f:1}"); + .assertBody().is(""); + } + + @Test + public void a20_basicCalls_head_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .simpleJson() + .build() + .head(url) + .run() + .assertBody().is(""); + } + } + @Test + public void a21_basicCalls_formPost() throws Exception { MockRestClient .create(A.class) .build() - .formPost("/bean", NameValuePairs.of("f","1")) + .formPost("/bean", bean) .accept("application/json+simple") .run() .assertBody().is("{f:1}"); + } + @Test + public void a22_basicCalls_formPost_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .build() + .formPost(url, bean) + .accept("application/json+simple") + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a23_basicCalls_formPost_exhaustiveBodyTypes() throws Exception { + List<Object> bodies = AList.of( + bean, + NameValuePairs.of("f","1"), + new NameValuePair[]{BasicNameValuePair.of("f","1")}, + new StringEntity("{f:1}", org.apache.http.entity.ContentType.APPLICATION_JSON), + BasicNameValuePair.of("f","1") + ); + + for (Object body : bodies) { + MockRestClient + .create(A.class) + .build() + .formPost("/bean", body) + .accept("application/json+simple") + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a24_basicCalls_formPostPairs() throws Exception { MockRestClient .create(A.class) .build() - .formPost("/bean", (Object)new NameValuePair[]{BasicNameValuePair.of("f","1")}) + .formPostPairs("/bean", new StringBuilder("f"), new StringBuilder("1")) .accept("application/json+simple") .run() .assertBody().is("{f:1}"); + } + @Test + public void a25_basicCalls_patch() throws Exception { MockRestClient .create(A.class) + .simpleJson() .build() - .formPost("/bean", new NameValuePair[]{BasicNameValuePair.of("f","1")}) - .accept("application/json+simple") + .patch("/bean", bean) .run() .assertBody().is("{f:1}"); MockRestClient .create(A.class) + .simpleJson() .build() - .formPost("/bean", new StringEntity("{f:1}", org.apache.http.entity.ContentType.APPLICATION_JSON)) - .accept("application/json+simple") + .patch("/bean") + .body(bean) .run() .assertBody().is("{f:1}"); + } + @Test + public void a26_basicCalls_patch_fromString() throws Exception { MockRestClient .create(A.class) + .simpleJson() .build() - .formPost("/bean", BasicNameValuePair.of("f","1")) - .accept("application/json+simple") + .patch("/bean", "{f:1}", "application/json") .run() .assertBody().is("{f:1}"); + } + @Test + public void a27_basicCalls_patch_exhaustiveBodyTypes() throws Exception { + List<Object> bodies = AList.<Object>of( + new StringReader("{f:1}"), + new ByteArrayInputStream("{f:1}".getBytes()), + ReaderResource.create().contents("{f:1}").build(), + StreamResource.create().contents("{f:1}").build(), + bean, + new StringEntity("{f:1}"), + NameValuePairs.of("f", 1) + ); + for (Object body : bodies) { + MockRestClient + .create(A.class) + .simpleJson() + .build() + .patch("/bean", body) + .contentType(body instanceof NameValuePairs ? "application/x-www-form-urlencoded" : "application/json") + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a28_basicCalls_patch_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .build() + .patch(url, bean) + .accept("application/json+simple") + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a29_basicCalls_request_patch() throws Exception { MockRestClient .create(A.class) + .simpleJson() .build() - .formPostPairs("/bean", "f", "1") - .accept("application/json+simple") + .request(HttpMethod.PATCH, "/bean", bean) .run() .assertBody().is("{f:1}"); - } - @Test - public void a12_basicCalls_patch() throws Exception { MockRestClient .create(A.class) .simpleJson() .build() - .patch("/bean", bean) + .request(HttpMethod.PATCH, "/bean") + .body(bean) .run() .assertBody().is("{f:1}"); + } + + @Test + public void a30_basicCalls_request_patch_exhaustiveBodyTypes() throws Exception { + List<Object> bodies = AList.<Object>of( + new StringReader("{f:1}"), + new ByteArrayInputStream("{f:1}".getBytes()), + ReaderResource.create().contents("{f:1}").build(), + StreamResource.create().contents("{f:1}").build(), + bean, + new StringEntity("{f:1}"), + NameValuePairs.of("f", 1) + ); + for (Object body : bodies) { + MockRestClient + .create(A.class) + .simpleJson() + .build() + .request(HttpMethod.PATCH, "/bean", body) + .contentType(body instanceof NameValuePairs ? "application/x-www-form-urlencoded" : "application/json") + .run() + .assertBody().is("{f:1}"); + } + } + @Test + public void a31_basicCalls_request_patch_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .build() + .request(HttpMethod.PATCH, url, bean) + .accept("application/json+simple") + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a32_basicCalls_request_get() throws Exception { MockRestClient .create(A.class) .simpleJson() .build() - .patch("/bean", "{f:1}", "application/json") + .request(HttpMethod.GET, "/bean", null) .run() .assertBody().is("{f:1}"); @@ -459,12 +751,65 @@ public class RestClientTest { .create(A.class) .simpleJson() .build() - .patch("/bean") - .body(bean) + .request(HttpMethod.GET, "/bean") .run() .assertBody().is("{f:1}"); } + @Test + public void a33_basicCalls_request_get_exhaustiveUrls() throws Exception { + List<Object> urls = AList.<Object>of( + new URIBuilder("http://localhost/bean"), + java.net.URI.create("http://localhost/bean"), + new URL("http://localhost/bean"), + "/bean", + new StringBuilder("/bean") + ); + + for (Object url : urls) { + MockRestClient + .create(A.class) + .build() + .request(HttpMethod.GET, url) + .accept("application/json+simple") + .run() + .assertBody().is("{f:1}"); + } + } + + @Test + public void a34_basicCalls_request_whenClosed() throws Exception { + RestClient rc = MockRestClient + .create(A.class) + .build(); + + rc.closeQuietly(); + + try { + rc.request(HttpMethod.GET, "/bean", null); + fail(); + } catch (RestCallException e) { + assertTrue(e.getMessage().startsWith("RestClient.close() has already been called")); + } + } + + @Test + public void a35_basicCalls_request_whenClosed_withStackCreation() throws Exception { + RestClient rc = MockRestClient + .create(A.class) + .debug() + .build(); + + rc.closeQuietly(); + + try { + rc.request(HttpMethod.GET, "/bean", null); + fail(); + } catch (RestCallException e) { + assertTrue(e.getMessage().startsWith("RestClient.close() has already been called")); + } + } + //------------------------------------------------------------------------------------------------------------------ // Logging //------------------------------------------------------------------------------------------------------------------ diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java index 60c35a9..fdda46f 100644 --- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java +++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java @@ -17,6 +17,8 @@ import static org.apache.juneau.AddFlag.*; import static org.apache.juneau.httppart.HttpPartType.*; import static org.apache.juneau.rest.client2.RestClientUtils.*; import static java.util.logging.Level.*; +import static org.apache.juneau.internal.StateMachineState.*; +import static java.lang.Character.*; import java.io.*; import java.lang.reflect.*; @@ -58,10 +60,10 @@ import org.apache.juneau.http.remote.*; import org.apache.juneau.httppart.*; import org.apache.juneau.httppart.bean.*; import org.apache.juneau.internal.*; -import org.apache.juneau.json.*; import org.apache.juneau.marshall.*; import org.apache.juneau.oapi.*; import org.apache.juneau.parser.*; +import org.apache.juneau.parser.ParseException; import org.apache.juneau.reflect.*; import org.apache.juneau.remote.*; import org.apache.juneau.rest.client.remote.*; @@ -2192,7 +2194,7 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re * @throws RestCallException If any authentication errors occurred. */ public RestRequest put(Object url, String body, String contentType) throws RestCallException { - return request("PUT", url, true).stringBody(body).contentType(contentType); + return request("PUT", url, true).bodyString(body).contentType(contentType); } /** @@ -2290,7 +2292,7 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re * @throws RestCallException If any authentication errors occurred. */ public RestRequest post(Object url, String body, String contentType) throws RestCallException { - return request("POST", url, true).stringBody(body).contentType(contentType); + return request("POST", url, true).bodyString(body).contentType(contentType); } /** @@ -2593,7 +2595,7 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re * @throws RestCallException If any authentication errors occurred. */ public RestRequest patch(Object url, String body, String contentType) throws RestCallException { - return request("PATCH", url, true).stringBody(body).contentType(contentType); + return request("PATCH", url, true).bodyString(body).contentType(contentType); } /** @@ -2649,50 +2651,66 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re * @throws RestCallException REST call failed. */ public RestRequest callback(String callString) throws RestCallException { - String s = callString; - try { - RestRequest rc = null; - String method = null, uri = null, content = null; - OMap h = null; - int i = s.indexOf(' '); - if (i != -1) { - method = s.substring(0, i).trim(); - s = s.substring(i).trim(); - if (s.length() > 0) { - if (s.charAt(0) == '{') { - i = s.indexOf('}'); - if (i != -1) { - String json = s.substring(0, i+1); - h = JsonParser.DEFAULT.parse(json, OMap.class); - s = s.substring(i+1).trim(); - } - } - if (s.length() > 0) { - i = s.indexOf(' '); - if (i == -1) - uri = s; - else { - uri = s.substring(0, i).trim(); - s = s.substring(i).trim(); - if (s.length() > 0) - content = s; - } - } + callString = emptyIfNull(callString); + + // S01 - Looking for end of method. + // S02 - Found end of method, looking for beginning of URL or headers. + // S03 - Found beginning of headers, looking for end of headers. + // S04 - Found end of headers, looking for beginning of URL. + // S05 - Found beginning of URL, looking for end of URL. + + StateMachineState state = S01; + + int mark = 0; + String method = null, headers = null, uri = null, content = null; + for (int i = 0; i < callString.length(); i++) { + char c = callString.charAt(i); + if (state == S01) { + if (isWhitespace(c)) { + method = callString.substring(mark, i); + state = S02; + } + } else if (state == S02) { + if (! isWhitespace(c)) { + mark = i; + if (c == '{') + state = S03; + else + state = S05; + } + } else if (state == S03) { + if (c == '}') { + headers = callString.substring(mark, i+1); + state = S04; + } + } else if (state == S04) { + if (! isWhitespace(c)) { + mark = i; + state = S05; + } + } else /* (state == S05) */ { + if (isWhitespace(c)) { + uri = callString.substring(mark, i); + content = callString.substring(i).trim(); + break; } } - if (method != null && uri != null) { - rc = request(method, uri, content != null); - if (content != null) - rc.body(new StringEntity(content)); - if (h != null) - for (Map.Entry<String,Object> e : h.entrySet()) - rc.header(e.getKey(), e.getValue()); - return rc; - } - } catch (Exception e) { - throw new RestCallException(e); } - throw new RestCallException("Invalid format for call string."); + + if (state != S05) + throw new RestCallException("Invalid format for call string. State=" + state); + + try { + RestRequest req = request(method, uri, isNotEmpty(content)); + if (headers != null) + for (Map.Entry<String,Object> e : OMap.ofJson(headers).entrySet()) + req.headerString(e.getKey(), e.getValue()); + if (isNotEmpty(content)) + req.bodyString(content); + return req; + } catch (ParseException e) { + throw new RestCallException(e, "Invalid format for call string."); + } } /** @@ -2801,26 +2819,21 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re throw new RestCallException("RestClient.close() has already been called. This client cannot be reused. Closed location stack trace can be displayed by setting the system property 'org.apache.juneau.rest.client2.RestClient.trackCreation' to true."); } - try { - RestRequest req = createRequest(toURI(url), method.toUpperCase(Locale.ENGLISH), hasBody); + RestRequest req = createRequest(toURI(url), method.toUpperCase(Locale.ENGLISH), hasBody); - for (Object o : headers) - req.header(toHeader(o)); + for (Object o : headers) + req.header(toHeader(o)); - for (Object o : query) - req.query(toQuery(o)); + for (Object o : query) + req.query(toQuery(o)); - for (Object o : formData) - req.formData(toFormData(o)); + for (Object o : formData) + req.formData(toFormData(o)); - req.interceptors(this); - req.interceptors(interceptors); + req.interceptors(this); + req.interceptors(interceptors); - return req; - - } catch (URISyntaxException e1) { - throw new RestCallException(e1); - } + return req; } /** @@ -3599,27 +3612,31 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re private Pattern absUrlPattern = Pattern.compile("^\\w+\\:\\/\\/.*"); - URI toURI(Object url) throws URISyntaxException { - if (url instanceof URI) - return (URI)url; - if (url instanceof URL) - ((URL)url).toURI(); - if (url instanceof URIBuilder) - return ((URIBuilder)url).build(); - String s = url == null ? "" : url.toString(); - if (rootUrl != null && ! absUrlPattern.matcher(s).matches()) { - if (s.isEmpty()) - s = rootUrl; - else { - StringBuilder sb = new StringBuilder(rootUrl); - if (! s.startsWith("/")) - sb.append('/'); - sb.append(s); - s = sb.toString(); + URI toURI(Object url) throws RestCallException { + try { + if (url instanceof URI) + return (URI)url; + if (url instanceof URL) + ((URL)url).toURI(); + if (url instanceof URIBuilder) + return ((URIBuilder)url).build(); + String s = url == null ? "" : url.toString(); + if (rootUrl != null && ! absUrlPattern.matcher(s).matches()) { + if (s.isEmpty()) + s = rootUrl; + else { + StringBuilder sb = new StringBuilder(rootUrl); + if (! s.startsWith("/")) + sb.append('/'); + sb.append(s); + s = sb.toString(); + } } + s = fixUrl(s); + return new URI(s); + } catch (URISyntaxException e) { + throw new RestCallException(e, "Invalid URL encountered: " + url); // Shouldn't happen. } - s = fixUrl(s); - return new URI(s); } ExecutorService getExecutorService(boolean create) { diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClientBuilder.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClientBuilder.java index 4421d9b..479718b 100644 --- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClientBuilder.java +++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClientBuilder.java @@ -1220,6 +1220,39 @@ public class RestClientBuilder extends BeanContextBuilder { /** * Sets a header on all requests. * + * <p> + * Unlike {@link #header(String,Object)} which converts the value to a string using the part serializer, this + * method converts the value to a string using {@link Object#toString()}. + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * RestClient c = RestClient + * .<jsm>create</jsm>() + * .headerString(<js>"Foo"</js>, <js>"bar"</js>); + * .build(); + * </p> + * + * <ul class='seealso'> + * <li class='jf'>{@link RestClient#RESTCLIENT_headers} + * </ul> + * + * @param name The header name. + * @param value The header value. + * <ul> + * <li>Can be any POJO. + * <li>Converted to a string using {@link Object#toString()}. + * <li>Values are converted to strings at runtime to allow them to be modified externally. + * </ul> + * @return This object (for method chaining). + */ + @FluentSetter + public RestClientBuilder headerString(String name, Object value) { + return header(BasicStringHeader.of(name, value)); + } + + /** + * Sets a header on all requests. + * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> * RestClient c = RestClient @@ -1983,6 +2016,35 @@ public class RestClientBuilder extends BeanContextBuilder { /** * Adds a query parameter to the URI. * + * <p> + * Unlike {@link #query(String,Object)} which converts the value to a string using the part serializer, this + * method converts the value to a string using {@link Object#toString()}. + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * RestClient c = RestClient + * .<jsm>create</jsm>() + * .queryString(<js>"foo"</js>, <js>"bar"</js>) + * .build(); + * </p> + * + * @param name The parameter name. + * @param value The parameter value. + * <ul> + * <li>Can be any POJO. + * <li>Converted to a string using {@link Object#toString()}. + * <li>Values are converted to strings at runtime to allow them to be modified externally. + * </ul> + * @return This object (for method chaining). + */ + @FluentSetter + public RestClientBuilder queryString(String name, Object value) { + return query(BasicNameValuePair.of(name, value)); + } + + /** + * Adds a query parameter to the URI. + * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> * RestClient c = RestClient @@ -2253,6 +2315,35 @@ public class RestClientBuilder extends BeanContextBuilder { /** * Adds a form-data parameter to all request bodies. * + * <p> + * Unlike {@link #formData(String,Object)} which converts the value to a string using the part serializer, this + * method converts the value to a string using {@link Object#toString()}. + + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * RestClient c = RestClient + * .<jsm>create</jsm>() + * .formDataString(<js>"foo"</js>, <js>"bar"</js>) + * .build(); + * </p> + * + * @param name The parameter name. + * @param value The parameter value. + * <ul> + * <li>Can be any POJO. + * <li>Converted to a string using {@link Object#toString()}. + * <li>Values are converted to strings at runtime to allow them to be modified externally. + * </ul> + * @return This object (for method chaining). + */ + @FluentSetter + public RestClientBuilder formDataString(String name, Object value) { + return formData(BasicNameValuePair.of(name, value)); + } + + /** + * Adds a form-data parameter to all request bodies. + * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> * RestClient c = RestClient diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java index e8f1eb8..fc8f0f7 100644 --- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java +++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java @@ -790,13 +790,9 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur * @throws RestCallException Invalid URI syntax detected. */ public RestRequest uri(Object uri) throws RestCallException { - try { - if (uri != null) - uriBuilder = new URIBuilder(client.toURI(uri)); - return this; - } catch (URISyntaxException e) { - throw new RestCallException(e); - } + if (uri != null) + uriBuilder = new URIBuilder(client.toURI(uri)); + return this; } /** @@ -898,6 +894,36 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur /** * Replaces a path parameter of the form <js>"{name}"</js> in the URL. * + * <p> + * Unlike {@link #path(String,Object)} which converts the value to a string using the part serializer, this + * method converts the value to a string using {@link Object#toString()}. + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * client + * .get(<js>"/{foo}"</js>) + * .pathString(<js>"foo"</js>, <js>"bar"</js>) + * .run(); + * </p> + * + * @param name The parameter name. + * @param value The parameter value. + * <ul> + * <li>Can be any POJO. + * <li>Converted to a string using {@link Object#toString()}. + * <li>Values are converted to strings at runtime to allow them to be modified externally. + * </ul> + * @return This object (for method chaining). + * @throws RestCallException Invalid input. + */ + @FluentSetter + public RestRequest pathString(String name, Object value) throws RestCallException { + return path(BasicNameValuePair.of(name, value)); + } + + /** + * Replaces a path parameter of the form <js>"{name}"</js> in the URL. + * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> * client @@ -1337,6 +1363,36 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur /** * Adds a query parameter to the URI. * + * <p> + * Unlike {@link #query(String,Object)} which converts the value to a string using the part serializer, this + * method converts the value to a string using {@link Object#toString()}. + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * client + * .get(<jsf>URL</jsf>) + * .queryString(<js>"foo"</js>, <js>"bar"</js>) + * .run(); + * </p> + * + * @param name The parameter name. + * @param value The parameter value. + * <ul> + * <li>Can be any POJO. + * <li>Converted to a string using {@link Object#toString()}. + * <li>Values are converted to strings at runtime to allow them to be modified externally. + * </ul> + * @return This object (for method chaining). + * @throws RestCallException Invalid input. + */ + @FluentSetter + public RestRequest queryString(String name, Object value) throws RestCallException { + return query(BasicNameValuePair.of(name, value)); + } + + /** + * Adds a query parameter to the URI. + * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> * client @@ -1975,6 +2031,36 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur /** * Adds a form-data parameter to the request body. * + * <p> + * Unlike {@link #formData(String,Object)} which converts the value to a string using the part serializer, this + * method converts the value to a string using {@link Object#toString()}. + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * client + * .formPost(<jsf>URL</jsf>) + * .formDataString(<js>"foo"</js>, <js>"bar"</js>) + * .run(); + * </p> + * + * @param name The parameter name. + * @param value The parameter value. + * <ul> + * <li>Can be any POJO. + * <li>Converted to a string using {@link Object#toString()}. + * <li>Values are converted to strings at runtime to allow them to be modified externally. + * </ul> + * @return This object (for method chaining). + * @throws RestCallException Invalid input. + */ + @FluentSetter + public RestRequest formDataString(String name, Object value) throws RestCallException { + return formData(BasicNameValuePair.of(name, value)); + } + + /** + * Adds a form-data parameter to the request body. + * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> * client @@ -2499,7 +2585,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur * .run(); * * client.put(<js>"/foo"</js>) - * .stringBody(<js>"foo"</js>) + * .bodyString(<js>"foo"</js>) * .run(); * </p> * @@ -2517,8 +2603,8 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur * @return This object (for method chaining). * @throws RestCallException If a retry was attempted, but the entity was not repeatable. */ - public RestRequest stringBody(String input) throws RestCallException { - return body(input == null ? null : new StringReader(input)); + public RestRequest bodyString(Object input) throws RestCallException { + return body(input == null ? null : new StringReader(stringify(input))); } /** @@ -2707,6 +2793,36 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur /** * Appends a header on the request. * + * <p> + * Unlike {@link #header(String,Object)} which converts the value to a string using the part serializer, this + * method converts the value to a string using {@link Object#toString()}. + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * client + * .get(<jsf>URL</jsf>) + * .headerString(<js>"Foo"</js>, <js>"bar"</js>) + * .run(); + * </p> + * + * @param name The header name. + * @param value The header value. + * <ul> + * <li>Can be any POJO. + * <li>Converted to a string using {@link Object#toString()}. + * <li>Values are converted to strings at runtime to allow them to be modified externally. + * </ul> + * @return This object (for method chaining). + * @throws RestCallException Invalid input. + */ + @FluentSetter + public RestRequest headerString(String name, Object value) throws RestCallException { + return header(BasicStringHeader.of(name, value)); + } + + /** + * Appends a header on the request. + * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> * client diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java index be9d498..e306a54 100644 --- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java +++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java @@ -561,6 +561,7 @@ public class MockRestClient extends RestClient implements HttpClientConnection { @Override /* HttpClientConnection */ public void close() throws IOException { + // Don't call super.close() because it will close the client. rreq.remove(); rres.remove(); sreq.remove(); diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java index 6347cc9..261bb53 100644 --- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java +++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java @@ -922,6 +922,12 @@ public class MockRestClientBuilder extends RestClientBuilder { } @Override /* GENERATED - RestClientBuilder */ + public MockRestClientBuilder formDataString(String name, Object value) { + super.formDataString(name, value); + return this; + } + + @Override /* GENERATED - RestClientBuilder */ public MockRestClientBuilder forwarded(Object value) { super.forwarded(value); return this; @@ -982,6 +988,12 @@ public class MockRestClientBuilder extends RestClientBuilder { } @Override /* GENERATED - RestClientBuilder */ + public MockRestClientBuilder headerString(String name, Object value) { + super.headerString(name, value); + return this; + } + + @Override /* GENERATED - RestClientBuilder */ public MockRestClientBuilder headers(Object...headers) { super.headers(headers); return this; @@ -1374,6 +1386,12 @@ public class MockRestClientBuilder extends RestClientBuilder { } @Override /* GENERATED - RestClientBuilder */ + public MockRestClientBuilder queryString(String name, Object value) { + super.queryString(name, value); + return this; + } + + @Override /* GENERATED - RestClientBuilder */ public MockRestClientBuilder quoteChar(char value) { super.quoteChar(value); return this;