![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.juneau.rest.client.ResponseContent Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.rest.client;
import static org.apache.juneau.common.internal.IOUtils.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.common.internal.ThrowableUtils.*;
import java.io.*;
import java.lang.reflect.*;
import java.nio.charset.*;
import java.util.concurrent.*;
import java.util.regex.*;
import java.util.regex.Matcher;
import org.apache.http.*;
import org.apache.http.conn.*;
import org.apache.juneau.*;
import org.apache.juneau.assertions.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.common.internal.*;
import org.apache.juneau.http.entity.*;
import org.apache.juneau.http.resource.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.oapi.*;
import org.apache.juneau.objecttools.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.parser.ParseException;
import org.apache.juneau.reflect.*;
import org.apache.juneau.rest.client.assertion.*;
/**
* Represents the body of an HTTP response.
*
*
* An extension of an HttpClient {@link HttpEntity} that provides various support for converting the body to POJOs and
* other convenience methods.
*
*
See Also:
*/
public class ResponseContent implements HttpEntity {
private static final HttpEntity NULL_ENTITY = new HttpEntity() {
@Override
public boolean isRepeatable() {
return false;
}
@Override
public boolean isChunked() {
return false;
}
@Override
public long getContentLength() {
return -1;
}
@Override
public Header getContentType() {
return ResponseHeader.NULL_HEADER;
}
@Override
public Header getContentEncoding() {
return ResponseHeader.NULL_HEADER;
}
@Override
public InputStream getContent() throws IOException, UnsupportedOperationException {
return new ByteArrayInputStream(new byte[0]);
}
@Override
public void writeTo(OutputStream outstream) throws IOException {}
@Override
public boolean isStreaming() {
return false;
}
@Override
public void consumeContent() throws IOException {}
};
private final RestClient client;
final RestRequest request;
final RestResponse response;
private final HttpEntity entity;
private HttpPartSchema schema;
private Parser parser;
private byte[] body;
private boolean cached;
boolean isConsumed;
/**
* Constructor.
*
* @param client The client used to build this request.
* @param request The request object.
* @param response The response object.
* @param parser The parser to use to consume the body. Can be null .
*/
public ResponseContent(RestClient client, RestRequest request, RestResponse response, Parser parser) {
this.client = client;
this.request = request;
this.response = response;
this.parser = parser;
this.entity = ObjectUtils.firstNonNull(response.asHttpResponse().getEntity(), NULL_ENTITY);
}
//------------------------------------------------------------------------------------------------------------------
// Setters
//------------------------------------------------------------------------------------------------------------------
/**
* Specifies the parser to use for this body.
*
*
* If not specified, uses the parser defined on the client set via {@link RestClient.Builder#parser(Class)}.
*
* @param value
* The new part parser to use for this body.
* @return This object.
*/
public ResponseContent parser(Parser value) {
this.parser = value;
return this;
}
/**
* Specifies the schema for this body.
*
*
* Used by schema-based parsers such as {@link OpenApiParser}.
*
* @param value The schema.
* @return This object.
*/
public ResponseContent schema(HttpPartSchema value) {
this.schema = value;
return this;
}
/**
* Causes the contents of the response body to be stored so that it can be repeatedly read.
*
*
* Calling this method allows methods that read the response body to be called multiple times.
*
*
Notes:
* -
* Multiple calls to this method are ignored.
*
*
* @return This object.
*/
public ResponseContent cache() {
this.cached = true;
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Raw streams
//------------------------------------------------------------------------------------------------------------------
/**
* Returns the HTTP response message body as an input stream.
*
* Notes:
* -
* Once this input stream is exhausted, it will automatically be closed.
*
-
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @return
* The HTTP response message body input stream, never null .
*
For responses without a body(e.g. HTTP 204), returns an empty stream.
* @throws IOException If a stream or illegal state exception was thrown.
*/
@SuppressWarnings("resource")
public InputStream asInputStream() throws IOException {
try {
if (body != null)
return new ByteArrayInputStream(body);
if (cached) {
body = readBytes(entity.getContent());
response.close();
return new ByteArrayInputStream(body);
}
if (isConsumed && ! entity.isRepeatable())
throw new IllegalStateException("Method cannot be called. Response has already been consumed. Consider using the RestResponse.cacheBody() method.");
HttpEntity e = response.asHttpResponse().getEntity();
InputStream is = e == null ? new ByteArrayInputStream(new byte[0]) : e.getContent();
is = new EofSensorInputStream(is, new EofSensorWatcher() {
@Override
public boolean eofDetected(InputStream wrapped) throws IOException {
try {
response.close();
} catch (RestCallException e) {}
return true;
}
@Override
public boolean streamClosed(InputStream wrapped) throws IOException {
try {
response.close();
} catch (RestCallException e) {}
return true;
}
@Override
public boolean streamAbort(InputStream wrapped) throws IOException {
try {
response.close();
} catch (RestCallException e) {}
return true;
}
});
isConsumed = true;
return is;
} catch (UnsupportedOperationException | RestCallException e) {
throw new IOException(e);
}
}
/**
* Returns the HTTP response message body as a reader based on the charset on the Content-Type
response header.
*
* Notes:
* -
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* Once this input stream is exhausted, it will automatically be closed.
*
-
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @return
* The HTTP response message body reader, never null .
*
For responses without a body(e.g. HTTP 204), returns an empty reader.
* @throws IOException If an exception occurred.
*/
public Reader asReader() throws IOException {
// Figure out what the charset of the response is.
String cs = null;
String ct = getContentType().orElse(null);
// First look for "charset=" in Content-Type header of response.
if (ct != null)
if (ct.contains("charset="))
cs = ct.substring(ct.indexOf("charset=")+8).trim();
return asReader(cs == null ? IOUtils.UTF8 : Charset.forName(cs));
}
/**
* Returns the HTTP response message body as a reader using the specified charset.
*
* Notes:
* -
* Once this input stream is exhausted, it will automatically be closed.
*
-
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @param charset
* The charset to use for the reader.
*
If null , "UTF-8" is used.
* @return
* The HTTP response message body reader, never null .
*
For responses without a body(e.g. HTTP 204), returns an empty reader.
* @throws IOException If an exception occurred.
*/
public Reader asReader(Charset charset) throws IOException {
return new InputStreamReader(asInputStream(), charset == null ? IOUtils.UTF8 : charset);
}
/**
* Returns the HTTP response message body as a byte array.
*
* The HTTP response message body reader, never null .
*
For responses without a body(e.g. HTTP 204), returns an empty array.
*
* @return The HTTP response body as a byte array.
* @throws RestCallException If an exception occurred.
*/
public byte[] asBytes() throws RestCallException {
if (body == null) {
try {
if (entity instanceof BasicHttpEntity) {
body = ((BasicHttpEntity)entity).asBytes();
} else {
body = readBytes(entity.getContent());
}
} catch (IOException e) {
throw new RestCallException(response, e, "Could not read response body.");
} finally {
response.close();
}
}
return body;
}
/**
* Pipes the contents of the response to the specified output stream.
*
* Notes:
* -
* The output stream is not automatically closed.
*
-
* Once the input stream is exhausted, it will automatically be closed.
*
-
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @param os The output stream to pipe the output to.
* @return This object.
* @throws IOException If an IO exception occurred.
*/
public RestResponse pipeTo(OutputStream os) throws IOException {
pipe(asInputStream(), os);
return response;
}
/**
* Pipes the contents of the response to the specified writer.
*
* Notes:
* -
* The writer is not automatically closed.
*
-
* Once the reader is exhausted, it will automatically be closed.
*
-
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @param w The writer to pipe the output to.
* @return This object.
* @throws IOException If an IO exception occurred.
*/
public RestResponse pipeTo(Writer w) throws IOException {
return pipeTo(w, false);
}
/**
* Pipes the contents of the response to the specified writer.
*
* Notes:
* -
* The writer is not automatically closed.
*
-
* Once the reader is exhausted, it will automatically be closed.
*
-
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @param w The writer to pipe the output to.
* @param charset
* The charset to use for the reader.
*
If null , "UTF-8" is used.
* @return This object.
* @throws IOException If an IO exception occurred.
*/
public RestResponse pipeTo(Writer w, Charset charset) throws IOException {
return pipeTo(w, charset, false);
}
/**
* Pipes the contents of the response to the specified writer.
*
* Notes:
* -
* The writer is not automatically closed.
*
-
* Once the reader is exhausted, it will automatically be closed.
*
-
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @param w The writer to write the output to.
* @param byLines Flush the writers after every line of output.
* @return This object.
* @throws IOException If an IO exception occurred.
*/
public RestResponse pipeTo(Writer w, boolean byLines) throws IOException {
return pipeTo(w, null, byLines);
}
/**
* Pipes the contents of the response to the specified writer.
*
* Notes:
* -
* The writer is not automatically closed.
*
-
* Once the reader is exhausted, it will automatically be closed.
*
-
* This method can be called multiple times if {@link #cache()} has been called.
*
-
* Calling this method multiple times without caching enabled will cause a {@link RestCallException}
* with an inner {@link IllegalStateException} to be thrown.
*
*
* @param w The writer to pipe the output to.
* @param byLines Flush the writers after every line of output.
* @param charset
* The charset to use for the reader.
*
If null , "UTF-8" is used.
* @return This object.
* @throws IOException If an IO exception occurred.
*/
public RestResponse pipeTo(Writer w, Charset charset, boolean byLines) throws IOException {
if (byLines)
pipeLines(asReader(charset), w);
else
pipe(asReader(charset), w);
return response;
}
//------------------------------------------------------------------------------------------------------------------
// Retrievers
//------------------------------------------------------------------------------------------------------------------
/**
* Parses HTTP body into the specified object type.
*
*
* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps).
*
*
Examples:
*
* // Parse into a linked-list of strings.
* List<String> list1 = client
* .get(URI )
* .run()
* .getContent().as(LinkedList.class , String.class );
*
* // Parse into a linked-list of beans.
* List<MyBean> list2 = client
* .get(URI )
* .run()
* .getContent().as(LinkedList.class , MyBean.class );
*
* // Parse into a linked-list of linked-lists of strings.
* List<List<String>> list3 = client
* .get(URI )
* .run()
* .getContent().as(LinkedList.class , LinkedList.class , String.class );
*
* // Parse into a map of string keys/values.
* Map<String,String> map1 = client
* .get(URI )
* .run()
* .getContent().as(TreeMap.class , String.class , String.class );
*
* // Parse into a map containing string keys and values of lists containing beans.
* Map<String,List<MyBean>> map2 = client
* .get(URI )
* .run()
* .getContent().as(TreeMap.class , String.class , List.class , MyBean.class );
*
*
*
* Collection classes are assumed to be followed by zero or one objects indicating the element type.
*
*
* Map classes are assumed to be followed by zero or two meta objects indicating the key and value types.
*
*
* The array can be arbitrarily long to indicate arbitrarily complex data structures.
*
*
Notes:
* -
* Use the {@link #as(Class)} method instead if you don't need a parameterized map/collection.
*
-
* You can also specify any of the following types:
*
* - {@link ResponseContent}/{@link HttpEntity} - Returns access to this object.
*
- {@link Reader} - Returns access to the raw reader of the response.
*
- {@link InputStream} - Returns access to the raw input stream of the response.
*
- {@link HttpResource} - Response will be converted to an {@link BasicResource}.
*
- Any type that takes in an {@link HttpResponse} object.
*
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after this call.
*
*
* @param The class type of the object to create.
* @param type
* The object type to create.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
* @param args
* The type arguments of the class if it's a collection or map.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
*
Ignored if the main type is not a map or collection.
* @return The parsed object.
* @throws RestCallException
*
* - If the input contains a syntax error or is malformed, or is not valid for the specified type.
*
- If a connection error occurred.
*
* @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections.
*/
public T as(Type type, Type...args) throws RestCallException {
return as(getClassMeta(type, args));
}
/**
* Same as {@link #as(Type,Type...)} except optimized for a non-parameterized class.
*
*
* This is the preferred parse method for simple types since you don't need to cast the results.
*
*
Examples:
*
* // Parse into a string.
* String string = client .get(URI ).run().getContent().as(String.class );
*
* // Parse into a bean.
* MyBean bean = client .get(URI ).run().getContent().as(MyBean.class );
*
* // Parse into a bean array.
* MyBean[] beanArray = client .get(URI ).run().getContent().as(MyBean[].class );
*
* // Parse into a linked-list of objects.
* List list = client .get(URI ).run().getContent().as(LinkedList.class );
*
* // Parse into a map of object keys/values.
* Map map = client .get(URI ).run().getContent().as(TreeMap.class );
*
*
* Notes:
* -
* You can also specify any of the following types:
*
* - {@link ResponseContent}/{@link HttpEntity} - Returns access to this object.
*
- {@link Reader} - Returns access to the raw reader of the response.
*
- {@link InputStream} - Returns access to the raw input stream of the response.
*
- {@link HttpResource} - Response will be converted to an {@link BasicResource}.
*
- Any type that takes in an {@link HttpResponse} object.
*
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after this call.
*
*
* @param
* The class type of the object being created.
* See {@link #as(Type,Type...)} for details.
* @param type The object type to create.
* @return The parsed object.
* @throws RestCallException
* If the input contains a syntax error or is malformed, or is not valid for the specified type, or if a connection
* error occurred.
*/
public T as(Class type) throws RestCallException {
return as(getClassMeta(type));
}
/**
* Same as {@link #as(Class)} except allows you to predefine complex data types using the {@link ClassMeta} API.
*
* Examples:
*
* BeanContext beanContext = BeanContext.DEFAULT ;
*
* // Parse into a linked-list of strings.
* ClassMeta<List<String>> cm1 = beanContext .getClassMeta(LinkedList.class , String.class );
* List<String> list1 = client .get(URI ).run().getContent().as(cm1 );
*
* // Parse into a linked-list of beans.
* ClassMeta<List<String>> cm2 = beanContext .getClassMeta(LinkedList.class , MyBean.class );
* List<MyBean> list2 = client .get(URI ).run().getContent().as(cm2 );
*
* // Parse into a linked-list of linked-lists of strings.
* ClassMeta<List<String>> cm3 = beanContext .getClassMeta(LinkedList.class , LinkedList.class , String.class );
* List<List<String>> list3 = client .get(URI ).run().getContent().as(cm3 );
*
* // Parse into a map of string keys/values.
* ClassMeta<List<String>> cm4 = beanContext .getClassMeta(TreeMap.class , String.class , String.class );
* Map<String,String> map4 = client .get(URI ).run().getContent().as(cm4 );
*
* // Parse into a map containing string keys and values of lists containing beans.
* ClassMeta<List<String>> cm5 = beanContext .getClassMeta(TreeMap.class , String.class , List.class , MyBean.class );
* Map<String,List<MyBean>> map5 = client .get(URI ).run().getContent().as(cm5 );
*
*
* Notes:
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after this call.
*
*
* @param The class type of the object to create.
* @param type The object type to create.
* @return The parsed object.
* @throws RestCallException
*
* - If the input contains a syntax error or is malformed, or is not valid for the specified type.
*
- If a connection error occurred.
*
* @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections.
*/
@SuppressWarnings("unchecked")
public T as(ClassMeta type) throws RestCallException {
try {
if (type.is(ResponseContent.class) || type.is(HttpEntity.class))
return (T)this;
if (type.is(Reader.class))
return (T)asReader();
if (type.is(InputStream.class))
return (T)asInputStream();
if (type.is(HttpResponse.class))
return (T)response;
if (type.is(HttpResource.class))
type = (ClassMeta)getClassMeta(BasicResource.class);
ConstructorInfo ci = type.getInfo().getPublicConstructor(x -> x.hasParamTypes(HttpResponse.class));
if (ci != null) {
try {
return (T)ci.invoke(response);
} catch (ExecutableException e) {
throw asRuntimeException(e);
}
}
String ct = firstNonEmpty(response.getHeader("Content-Type").orElse("text/plain"));
if (parser == null)
parser = client.getMatchingParser(ct);
MediaType mt = MediaType.of(ct);
if (parser == null || (mt.toString().contains("text/plain") && ! parser.canHandle(ct))) {
if (type.hasStringMutater())
return type.getStringMutater().mutate(asString());
}
if (parser != null) {
try (Closeable in = parser.isReaderParser() ? asReader() : asInputStream()) {
T t = parser
.createSession()
.properties(JsonMap.create().inner(request.getSessionProperties()))
.locale(response.getLocale())
.mediaType(mt)
.schema(schema)
.build()
.parse(in, type);
// Some HTTP responses have no body, so try to create these beans if they've got no-arg constructors.
if (t == null && ! type.is(String.class)) {
ConstructorInfo c = type.getInfo().getPublicConstructor(ConstructorInfo::hasNoParams);
if (c != null) {
try {
return c.invoke();
} catch (ExecutableException e) {
throw new ParseException(e);
}
}
}
return t;
}
}
if (type.hasReaderMutater())
return type.getReaderMutater().mutate(asReader());
if (type.hasInputStreamMutater())
return type.getInputStreamMutater().mutate(asInputStream());
ct = response.getStringHeader("Content-Type").orElse(null);
if (ct == null && client.hasParsers())
throw new ParseException("Content-Type not specified in response header. Cannot find appropriate parser.");
throw new ParseException("Unsupported media-type in request header ''Content-Type'': ''{0}''", ct);
} catch (ParseException | IOException e) {
response.close();
throw new RestCallException(response, e, "Could not parse response body.");
}
}
/**
* Same as {@link #as(Class)} but allows you to run the call asynchronously.
*
* Notes:
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after the execution of the future.
*
*
* @param The class type of the object being created.
* @param type The object type to create.
* @return The future object.
* @throws RestCallException If the executor service was not defined.
* @see
* RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating
* {@link Future Futures}.
*/
public Future asFuture(final Class type) throws RestCallException {
return client.getExecutorService().submit(() -> as(type));
}
/**
* Same as {@link #as(ClassMeta)} but allows you to run the call asynchronously.
*
* Notes:
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after the execution of the future.
*
*
* @param
* The class type of the object being created.
* See {@link #as(Type, Type...)} for details.
* @param type The object type to create.
* @return The future object.
* @throws RestCallException If the executor service was not defined.
* @see
* RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating
* {@link Future Futures}.
*/
public Future asFuture(final ClassMeta type) throws RestCallException {
return client.getExecutorService().submit(() -> as(type));
}
/**
* Same as {@link #as(Type,Type...)} but allows you to run the call asynchronously.
*
* Notes:
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after the execution of the future.
*
*
* @param
* The class type of the object being created.
* See {@link #as(Type, Type...)} for details.
* @param type
* The object type to create.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
* @param args
* The type arguments of the class if it's a collection or map.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
*
Ignored if the main type is not a map or collection.
* @return The future object.
* @throws RestCallException If the executor service was not defined.
* @see
* RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating
* {@link Future Futures}.
*/
public Future asFuture(final Type type, final Type... args) throws RestCallException {
return client.getExecutorService().submit(() -> as(type, args));
}
/**
* Returns the contents of this body as a string.
*
* Notes:
* -
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* This method automatically calls {@link #cache()} so that the body can be retrieved multiple times.
*
-
* The input stream is automatically closed after this call.
*
*
* @return The response as a string.
* @throws RestCallException
*
* - If the input contains a syntax error or is malformed, or is not valid for the specified type.
*
- If a connection error occurred.
*
*/
public String asString() throws RestCallException {
cache();
try (Reader r = asReader()) {
return read(r);
} catch (IOException e) {
response.close();
throw new RestCallException(response, e, "Could not read response body.");
}
}
/**
* Same as {@link #asString()} but allows you to run the call asynchronously.
*
* Notes:
* -
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* This method automatically calls {@link #cache()} so that the body can be retrieved multiple times.
*
-
* The input stream is automatically closed after this call.
*
*
* @return The future object.
* @throws RestCallException If the executor service was not defined.
* @see
* RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating
* {@link Future Futures}.
*/
public Future asStringFuture() throws RestCallException {
return client.getExecutorService().submit(this::asString);
}
/**
* Same as {@link #asString()} but truncates the string to the specified length.
*
*
* If truncation occurs, the string will be suffixed with "..." .
*
* @param length The max length of the returned string.
* @return The truncated string.
* @throws RestCallException If a problem occurred trying to read from the reader.
*/
public String asAbbreviatedString(int length) throws RestCallException {
return StringUtils.abbreviate(asString(), length);
}
/**
* Returns the HTTP body content as a simple hexadecimal character string.
*
*
Example:
*
* 0123456789ABCDEF
*
*
* @return The incoming input from the connection as a plain string.
* @throws RestCallException If a problem occurred trying to read from the reader.
*/
public String asHex() throws RestCallException {
return toHex(asBytes());
}
/**
* Returns the HTTP body content as a simple space-delimited hexadecimal character string.
*
* Example:
*
* 01 23 45 67 89 AB CD EF
*
*
* @return The incoming input from the connection as a plain string.
* @throws RestCallException If a problem occurred trying to read from the reader.
*/
public String asSpacedHex() throws RestCallException {
return toSpacedHex(asBytes());
}
/**
* Parses the output from the body into the specified type and then wraps that in a {@link ObjectRest}.
*
*
* Useful if you want to quickly retrieve a single value from inside of a larger JSON document.
*
* @param innerType The class type of the POJO being wrapped.
* @return The parsed output wrapped in a {@link ObjectRest}.
* @throws RestCallException
*
* - If the input contains a syntax error or is malformed, or is not valid for the specified type.
*
- If a connection error occurred.
*
*/
public ObjectRest asObjectRest(Class> innerType) throws RestCallException {
return new ObjectRest(as(innerType));
}
/**
* Converts the output from the connection into an {@link JsonMap} and then wraps that in a {@link ObjectRest}.
*
*
* Useful if you want to quickly retrieve a single value from inside of a larger JSON document.
*
* @return The parsed output wrapped in a {@link ObjectRest}.
* @throws RestCallException
*
* - If the input contains a syntax error or is malformed, or is not valid for the specified type.
*
- If a connection error occurred.
*
*/
public ObjectRest asObjectRest() throws RestCallException {
return asObjectRest(JsonMap.class);
}
/**
* Converts the contents of the response body to a string and then matches the specified pattern against it.
*
* Example:
*
* // Parse response using a regular expression.
* Matcher matcher = client
* .get(URI )
* .run()
* .getContent().asMatcher(Pattern.compile ("foo=(.*)" ));
*
* if (matcher .matches()) {
* String foo = matcher .group(1);
* }
*
*
* Notes:
* -
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* This method automatically calls {@link #cache()} so that the body can be retrieved multiple times.
*
-
* The input stream is automatically closed after this call.
*
*
* @param pattern The regular expression pattern to match.
* @return The matcher.
* @throws RestCallException If a connection error occurred.
*/
public Matcher asMatcher(Pattern pattern) throws RestCallException {
return pattern.matcher(asString());
}
/**
* Converts the contents of the response body to a string and then matches the specified pattern against it.
*
* Example:
*
* // Parse response using a regular expression.
* Matcher matcher = client
* .get(URI )
* .run()
* .getContent().asMatcher("foo=(.*)" );
*
* if (matcher .matches()) {
* String foo = matcher .group(1);
* }
*
*
*
* Notes:
* -
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after this call.
*
*
* @param regex The regular expression pattern to match.
* @return The matcher.
* @throws RestCallException If a connection error occurred.
*/
public Matcher asMatcher(String regex) throws RestCallException {
return asMatcher(regex, 0);
}
/**
* Converts the contents of the response body to a string and then matches the specified pattern against it.
*
* Example:
*
* // Parse response using a regular expression.
* Matcher matcher = client
* .get(URI )
* .run()
* .getContent().asMatcher("foo=(.*)" , MULTILINE & CASE_INSENSITIVE );
*
* if (matcher .matches()) {
* String foo = matcher .group(1);
* }
*
*
*
* Notes:
* -
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with
* other methods that retrieve the content of the response. Otherwise a {@link RestCallException}
* with an inner {@link IllegalStateException} will be thrown.
*
-
* The input stream is automatically closed after this call.
*
*
* @param regex The regular expression pattern to match.
* @param flags Pattern match flags. See {@link Pattern#compile(String, int)}.
* @return The matcher.
* @throws RestCallException If a connection error occurred.
*/
public Matcher asMatcher(String regex, int flags) throws RestCallException {
return asMatcher(Pattern.compile(regex, flags));
}
//------------------------------------------------------------------------------------------------------------------
// Assertions
//------------------------------------------------------------------------------------------------------------------
/**
* Provides the ability to perform fluent-style assertions on this response body.
*
*
* This method is called directly from the {@link RestResponse#assertContent()} method to instantiate a fluent assertions object.
*
*
Examples:
*
* // Validates the response body equals the text "OK".
* client
* .get(URI )
* .run()
* .getContent().assertValue().equals("OK" );
*
* // Validates the response body contains the text "OK".
* client
* .get(URI )
* .run()
* .getContent().assertValue().contains("OK" );
*
* // Validates the response body passes a predicate test.
* client
* .get(URI )
* .run()
* .getContent().assertValue().is(x -> x .contains("OK" ));
*
* // Validates the response body matches a regular expression.
* client
* .get(URI )
* .run()
* .getContent().assertValue().isPattern(".*OK.*" );
*
* // Validates the response body matches a regular expression using regex flags.
* client
* .get(URI )
* .run()
* .getContent().assertValue().isPattern(".*OK.*" , MULTILINE & CASE_INSENSITIVE );
*
* // Validates the response body matches a regular expression in the form of an existing Pattern.
* Pattern pattern = Pattern.compile (".*OK.*" );
* client
* .get(URI )
* .run()
* .getContent().assertValue().isPattern(pattern );
*
*
*
* The assertion test returns the original response object allowing you to chain multiple requests like so:
*
* // Validates the response body matches a regular expression.
* MyBean bean = client
* .get(URI )
* .run()
* .getContent().assertValue().isPattern(".*OK.*" );
* .getContent().assertValue().isNotPattern(".*ERROR.*" )
* .getContent().as(MyBean.class );
*
*
* Notes:
* -
* If no charset was found on the
Content-Type
response header, "UTF-8" is assumed.
* -
* This method automatically calls {@link #cache()} so that the body can be retrieved multiple times.
*
-
* The input stream is automatically closed after this call.
*
*
* @return A new fluent assertion object.
*/
public FluentResponseBodyAssertion assertValue() {
return new FluentResponseBodyAssertion<>(this, this);
}
/**
* Shortcut for calling assertValue().asString() .
*
* @return A new fluent assertion.
*/
public FluentStringAssertion assertString() {
return new FluentResponseBodyAssertion<>(this, this).asString();
}
/**
* Shortcut for calling assertValue().asBytes() .
*
* @return A new fluent assertion.
*/
public FluentByteArrayAssertion assertBytes() {
return new FluentResponseBodyAssertion<>(this, this).asBytes();
}
/**
* Shortcut for calling assertValue().as(type ) .
*
* @param The object type to create.
* @param type The object type to create.
* @return A new fluent assertion.
*/
public FluentAnyAssertion assertObject(Class type) {
return new FluentResponseBodyAssertion<>(this, this).as(type);
}
/**
* Shortcut for calling assertValue().as(type , args ) .
*
* @param The object type to create.
* @param type The object type to create.
* @param args Optional type arguments.
* @return A new fluent assertion.
*/
public FluentAnyAssertion