All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 assertObject(Type type, Type...args) { return new FluentResponseBodyAssertion<>(this, this).as(type, args); } /** * Returns the response that created this object. * * @return The response that created this object. */ public RestResponse response() { return response; } //------------------------------------------------------------------------------------------------------------------ // HttpEntity passthrough methods. //------------------------------------------------------------------------------------------------------------------ /** * Tells if the entity is capable of producing its data more than once. * *

* A repeatable entity's {@link #getContent()} and {@link #writeTo(OutputStream)} methods can be called more than * once whereas a non-repeatable entity's can not. * *

Notes:
    *
  • This method always returns true if the response body is cached (see {@link #cache()}). *
* * @return true if the entity is repeatable, false otherwise. */ @Override /* HttpEntity */ public boolean isRepeatable() { return cached || entity.isRepeatable(); } /** * Tells about chunked encoding for this entity. * *

* The primary purpose of this method is to indicate whether chunked encoding should be used when the entity is sent. *
For entities that are received, it can also indicate whether the entity was received with chunked encoding. * *

* The behavior of wrapping entities is implementation dependent, but should respect the primary purpose. * * @return true if chunked encoding is preferred for this entity, or false if it is not. */ @Override /* HttpEntity */ public boolean isChunked() { return entity.isChunked(); } /** * Tells the length of the content, if known. * * @return * The number of bytes of the content, or a negative number if unknown. *
If the content length is known but exceeds {@link Long#MAX_VALUE}, a negative number is returned. */ @Override /* HttpEntity */ public long getContentLength() { return body != null ? body.length : entity.getContentLength(); } /** * Obtains the Content-Type header, if known. * *

* This is the header that should be used when sending the entity, or the one that was received with the entity. * It can include a charset attribute. * * @return The Content-Type header for this entity, or null if the content type is unknown. */ @Override /* HttpEntity */ public ResponseHeader getContentType() { return new ResponseHeader("Content-Type", request, response, entity.getContentType()); } /** * Obtains the Content-Encoding header, if known. * *

* This is the header that should be used when sending the entity, or the one that was received with the entity. *
Wrapping entities that modify the content encoding should adjust this header accordingly. * * @return The Content-Encoding header for this entity, or null if the content encoding is unknown. */ @Override /* HttpEntity */ public ResponseHeader getContentEncoding() { return new ResponseHeader("Content-Encoding", request, response, entity.getContentEncoding()); } /** * Returns a content stream of the entity. * *

Notes:
    *
  • This method is equivalent to {@link #asInputStream()} which is the preferred method for fluent-style coding. *
  • This input stream will auto-close once the end of stream has been reached. *
  • It is up to the caller to properly close this stream if not fully consumed. *
  • This method can be called multiple times if the entity is repeatable or the cache flag is set on this object. *
  • Calling this method multiple times on a non-repeatable or cached body will throw a {@link IllegalStateException}. * Note that this is different from the HttpClient specs for this method. *
* * @return Content stream of the entity. */ @Override /* HttpEntity */ public InputStream getContent() throws IOException, UnsupportedOperationException { return asInputStream(); } /** * Writes the entity content out to the output stream. * *
Notes:
    *
  • This method is equivalent to {@link #pipeTo(OutputStream)} which is the preferred method for fluent-style coding. *
* * @param outstream The output stream to write entity content to. */ @Override /* HttpEntity */ public void writeTo(OutputStream outstream) throws IOException { pipeTo(outstream); } /** * Tells whether this entity depends on an underlying stream. * *
Notes:
    *
  • This method always returns false if the response body is cached (see {@link #cache()}. *
* * @return true if the entity content is streamed, false otherwise. */ @Override /* HttpEntity */ public boolean isStreaming() { return cached ? false : entity.isStreaming(); } /** * This method is called to indicate that the content of this entity is no longer required. * *

* This method is of particular importance for entities being received from a connection. *
The entity needs to be consumed completely in order to re-use the connection with keep-alive. * * @throws IOException If an I/O error occurs. * @deprecated Use standard java convention to ensure resource deallocation by calling {@link InputStream#close()} on * the input stream returned by {@link #getContent()} */ @Override /* HttpEntity */ @Deprecated public void consumeContent() throws IOException { entity.consumeContent(); } //------------------------------------------------------------------------------------------------------------------ // Utility methods //------------------------------------------------------------------------------------------------------------------ private BeanContext getBeanContext() { return parser == null ? BeanContext.DEFAULT : parser.getBeanContext(); } private ClassMeta getClassMeta(Class c) { return getBeanContext().getClassMeta(c); } private ClassMeta getClassMeta(Type type, Type...args) { return getBeanContext().getClassMeta(type, args); } @Override public String toString() { try { return asString(); } catch (RestCallException e) { return e.getLocalizedMessage(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy