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

org.apache.juneau.rest.client.RestResponse 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 org.apache.juneau.parser.*;
import org.apache.juneau.rest.client.assertion.*;

import static org.apache.juneau.httppart.HttpPartType.*;

import java.lang.reflect.*;
import java.text.*;
import java.util.*;
import java.util.logging.*;

import org.apache.http.*;
import org.apache.http.message.*;
import org.apache.http.params.*;
import org.apache.http.util.*;
import org.apache.juneau.*;
import org.apache.juneau.assertions.*;
import org.apache.juneau.common.internal.*;
import org.apache.juneau.http.header.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.httppart.bean.*;
import org.apache.juneau.internal.*;

/**
 * Represents a response from a remote REST resource.
 *
 * 

* Instances of this class are created by calling the {@link RestRequest#run()} method. * *

See Also:
*/ @FluentSetters public class RestResponse implements HttpResponse { private final RestClient client; private final RestRequest request; private final HttpResponse response; private final Parser parser; private ResponseContent responseContent; private boolean isClosed; private HeaderList headers; private Map partParserSessions = new IdentityHashMap<>(); private HttpPartParserSession partParserSession; /** * Constructor. * * @param client The RestClient that created this response. * @param request The REST request. * @param response The HTTP response. Can be null. * @param parser The overridden parser passed into {@link RestRequest#parser(Parser)}. */ protected RestResponse(RestClient client, RestRequest request, HttpResponse response, Parser parser) { this.client = client; this.request = request; this.parser = parser; this.response = response == null ? new BasicHttpResponse(null, 0, null) : response; this.responseContent = new ResponseContent(client, request, this, parser); this.headers = HeaderList.of(this.response.getAllHeaders()); } /** * Returns the request object that created this response object. * * @return The request object that created this response object. */ public RestRequest getRequest() { return request; } //------------------------------------------------------------------------------------------------------------------ // Setters //------------------------------------------------------------------------------------------------------------------ /** * Consumes the response body. * *

* This is equivalent to closing the input stream. * * @return This object. * @throws RestCallException If one of the {@link RestCallInterceptor RestCallInterceptors} threw an exception. */ @FluentSetter public RestResponse consume() throws RestCallException { close(); return this; } //------------------------------------------------------------------------------------------------------------------ // Status line //------------------------------------------------------------------------------------------------------------------ /** * Returns the status code of the response. * * Shortcut for calling getStatusLine().getStatusCode(). * * @return The status code of the response. */ public int getStatusCode() { return getStatusLine().getStatusCode(); } /** * Returns the status line reason phrase of the response. * * Shortcut for calling getStatusLine().getReasonPhrase(). * * @return The status line reason phrase of the response. */ public String getReasonPhrase() { return getStatusLine().getReasonPhrase(); } //------------------------------------------------------------------------------------------------------------------ // Status line assertions //------------------------------------------------------------------------------------------------------------------ /** * Provides the ability to perform fluent-style assertions on the response {@link StatusLine} object. * *

Examples:
*

* MyBean bean = client * .get(URI) * .run() * .assertStatus().asCode().is(200) * .getContent().as(MyBean.class); *

* * @return A new fluent assertion object. */ public FluentResponseStatusLineAssertion assertStatus() { return new FluentResponseStatusLineAssertion<>(getStatusLine(), this); } /** * Provides the ability to perform fluent-style assertions on the response status code. * *
Examples:
*

* MyBean bean = client * .get(URI) * .run() * .assertStatus(200) * .getContent().as(MyBean.class); *

* * @param value The value to assert. * @return A new fluent assertion object. */ public RestResponse assertStatus(int value) { assertStatus().asCode().is(value); return this; } //------------------------------------------------------------------------------------------------------------------ // Headers //------------------------------------------------------------------------------------------------------------------ /** * Shortcut for calling getHeader(name).asString(). * * @param name The header name. * @return The header value, never null */ public Optional getStringHeader(String name) { return getHeader(name).asString(); } /** * Shortcut for retrieving the response charset from the Content-Type header. * * @return The response charset. * @throws RestCallException If REST call failed. */ public String getCharacterEncoding() throws RestCallException { Optional ct = getContentType(); String s = null; if (ct.isPresent()) s = getContentType().get().getParameter("charset"); return StringUtils.isEmpty(s) ? "utf-8" : s; } /** * Shortcut for retrieving the response content type from the Content-Type header. * *

* This is equivalent to calling getHeader("Content-Type").as(ContentType.class). * * @return The response charset. * @throws RestCallException If REST call failed. */ public Optional getContentType() throws RestCallException { return getHeader("Content-Type").as(ContentType.class); } /** * Provides the ability to perform fluent-style assertions on the response character encoding. * *

Examples:
*

* // Validates that the response content charset is UTF-8. * client * .get(URI) * .run() * .assertCharset().is("utf-8"); *

* * @return A new fluent assertion object. * @throws RestCallException If REST call failed. */ public FluentStringAssertion assertCharset() throws RestCallException { return new FluentStringAssertion<>(getCharacterEncoding(), this); } /** * Provides the ability to perform fluent-style assertions on a response header. * *
Examples:
*

* // Validates the content type header is provided. * client * .get(URI) * .run() * .assertHeader("Content-Type").exists(); * * // Validates the content type is JSON. * client * .get(URI) * .run() * .assertHeader("Content-Type").is("application/json"); * * // Validates the content type is JSON using test predicate. * client * .get(URI) * .run() * .assertHeader("Content-Type").is(x -> x.equals("application/json")); * * // Validates the content type is JSON by just checking for substring. * client * .get(URI) * .run() * .assertHeader("Content-Type").contains("json"); * * // Validates the content type is JSON using regular expression. * client * .get(URI) * .run() * .assertHeader("Content-Type").isPattern(".*json.*"); * * // Validates the content type is JSON using case-insensitive regular expression. * client * .get(URI) * .run() * .assertHeader("Content-Type").isPattern(".*json.*", CASE_INSENSITIVE); *

* *

* The assertion test returns the original response object allowing you to chain multiple requests like so: *

* // Validates the header and converts it to a bean. * MediaType mediaType = client * .get(URI) * .run() * .assertHeader("Content-Type").isNotEmpty() * .assertHeader("Content-Type").isPattern(".*json.*") * .getHeader("Content-Type").as(MediaType.class); *

* * @param name The header name. * @return A new fluent assertion object. */ public FluentResponseHeaderAssertion assertHeader(String name) { return new FluentResponseHeaderAssertion<>(getHeader(name), this); } //------------------------------------------------------------------------------------------------------------------ // Body //------------------------------------------------------------------------------------------------------------------ /** * Returns the body of the response. * * This method can be called multiple times returning the same response body each time. * * @return The body of the response. */ public ResponseContent getContent() { return responseContent; } /** * Provides the ability to perform fluent-style assertions on this response body. * *
Examples:
*

* // Validates the response body equals the text "OK". * client * .get(URI) * .run() * .assertContent().is("OK"); * * // Validates the response body contains the text "OK". * client * .get(URI) * .run() * .assertContent().isContains("OK"); * * // Validates the response body passes a predicate test. * client * .get(URI) * .run() * .assertContent().is(x -> x.contains("OK")); * * // Validates the response body matches a regular expression. * client * .get(URI) * .run() * .assertContent().isPattern(".*OK.*"); * * // Validates the response body matches a regular expression using regex flags. * client * .get(URI) * .run() * .assertContent().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() * .assertContent().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() * .assertContent().isPattern(".*OK.*"); * .assertContent().isNotPattern(".*ERROR.*") * .getContent().as(MyBean.class); *

* *
Notes:
    *
  • * If no charset was found on the Content-Type response header, "UTF-8" is assumed. *
  • * When using this method, the body is automatically cached by calling the {@link ResponseContent#cache()}. *
  • * The input stream is automatically closed after this call. *
* * @return A new fluent assertion object. */ public FluentResponseBodyAssertion assertContent() { return new FluentResponseBodyAssertion<>(responseContent, this); } /** * Provides the ability to perform fluent-style assertions on this response body. * *

* A shortcut for calling assertContent().is(value). * *

Examples:
*

* // Validates the response body equals the text "OK". * client * .get(URI) * .run() * .assertContent("OK"); *

* * @param value The value to assert. * @return This object. */ public RestResponse assertContent(String value) { assertContent().is(value); return this; } /** * Provides the ability to perform fluent-style assertions on this response body. * *

* A shortcut for calling assertContent().asString().isMatches(value). * * @see FluentStringAssertion#isMatches(String) * @param value The value to assert. * @return This object. */ public RestResponse assertContentMatches(String value) { assertContent().asString().isMatches(value); return this; } /** * Caches the response body so that it can be read as a stream multiple times. * * This is equivalent to calling the following: *

* getContent().cache(); *

* * @return The body of the response. */ @FluentSetter public RestResponse cacheContent() { responseContent.cache(); return this; } @SuppressWarnings("unchecked") T as(ResponseBeanMeta rbm) { Class c = (Class)rbm.getClassMeta().getInnerClass(); final RestClient rc = this.client; return (T)Proxy.newProxyInstance( c.getClassLoader(), new Class[] { c }, (InvocationHandler) (proxy, method, args) -> { ResponseBeanPropertyMeta pm = rbm.getProperty(method.getName()); HttpPartParserSession pp = getPartParserSession(pm.getParser().orElse(rc.getPartParser())); HttpPartSchema schema = pm.getSchema(); HttpPartType pt = pm.getPartType(); String name = pm.getPartName().orElse(null); ClassMeta type = rc.getBeanContext().getClassMeta(method.getGenericReturnType()); if (pt == RESPONSE_HEADER) return getHeader(name).parser(pp).schema(schema).as(type).orElse(null); if (pt == RESPONSE_STATUS) return getStatusCode(); return getContent().schema(schema).as(type); }); } /** * Logs a message. * * @param level The log level. * @param t The throwable cause. * @param msg The message with {@link MessageFormat}-style arguments. * @param args The arguments. * @return This object. */ public RestResponse log(Level level, Throwable t, String msg, Object...args) { client.log(level, t, msg, args); return this; } /** * Logs a message. * * @param level The log level. * @param msg The message with {@link MessageFormat}-style arguments. * @param args The arguments. * @return This object. */ public RestResponse log(Level level, String msg, Object...args) { client.log(level, msg, args); return this; } // ----------------------------------------------------------------------------------------------------------------- // HttpResponse pass-through methods. // ----------------------------------------------------------------------------------------------------------------- /** * Obtains the status line of this response. * * The status line can be set using one of the setStatusLine methods, or it can be initialized in a constructor. * * @return The status line, or null if not yet set. */ @Override /* HttpResponse */ public ResponseStatusLine getStatusLine() { return new ResponseStatusLine(this, response.getStatusLine()); } /** * Sets the status line of this response. * * @param statusline The status line of this response */ @Override /* HttpResponse */ public void setStatusLine(StatusLine statusline) { response.setStatusLine(statusline); } /** * Sets the status line of this response. * *

* The reason phrase will be determined based on the current locale. * * @param ver The HTTP version. * @param code The status code. */ @Override /* HttpResponse */ public void setStatusLine(ProtocolVersion ver, int code) { response.setStatusLine(ver, code); } /** * Sets the status line of this response with a reason phrase. * * @param ver The HTTP version. * @param code The status code. * @param reason The reason phrase, or null to omit. */ @Override /* HttpResponse */ public void setStatusLine(ProtocolVersion ver, int code, String reason) { response.setStatusLine(ver, code, reason); } /** * Updates the status line of this response with a new status code. * * @param code The HTTP status code. * @throws IllegalStateException If the status line has not be set. */ @Override /* HttpResponse */ public void setStatusCode(int code) { response.setStatusCode(code); } /** * Updates the status line of this response with a new reason phrase. * * @param reason The new reason phrase as a single-line string, or null to unset the reason phrase. * @throws IllegalStateException If the status line has not be set. */ @Override /* HttpResponse */ public void setReasonPhrase(String reason) { response.setReasonPhrase(reason); } /** * Obtains the message entity of this response. * *

* The entity is provided by calling setEntity. * *

Notes:
    *
  • Unlike the {@link HttpResponse#getEntity()} method, this method never returns a null response. * Instead, getContent().isPresent() can be used to determine whether the response has a body. *
* * @return The response entity. Never null. */ @Override /* HttpResponse */ public ResponseContent getEntity() { return responseContent; } /** * Associates a response entity with this response. * *
Notes:
    *
  • If an entity has already been set for this response and it depends on an input stream * ({@link HttpEntity#isStreaming()} returns true), it must be fully consumed in order to ensure * release of resources. *
* * @param entity The entity to associate with this response, or null to unset. */ @Override /* HttpResponse */ public void setEntity(HttpEntity entity) { response.setEntity(entity); this.responseContent = new ResponseContent(client, request, this, parser); } /** * Obtains the locale of this response. * * The locale is used to determine the reason phrase for the status code. * It can be changed using {@link #setLocale(Locale)}. * * @return The locale of this response, never null. */ @Override /* HttpResponse */ public Locale getLocale() { return response.getLocale(); } /** * Changes the locale of this response. * * @param loc The new locale. */ @Override /* HttpResponse */ public void setLocale(Locale loc) { response.setLocale(loc); } /** * Returns the protocol version this message is compatible with. * * @return The protocol version this message is compatible with. */ @Override /* HttpMessage */ public ProtocolVersion getProtocolVersion() { return response.getProtocolVersion(); } /** * Checks if a certain header is present in this message. * *

* Header values are ignored. * * @param name The header name to check for. * @return true if at least one header with this name is present. */ @Override /* HttpMessage */ public boolean containsHeader(String name) { return response.containsHeader(name); } /** * Returns all the headers with a specified name of this message. * * Header values are ignored. *
Headers are ordered in the sequence they were sent over a connection. * * @param name The name of the headers to return. * @return All the headers with a specified name of this message. */ @Override /* HttpMessage */ public ResponseHeader[] getHeaders(String name) { return headers.stream(name).map(x -> new ResponseHeader(name, request, this, x).parser(getPartParserSession())).toArray(ResponseHeader[]::new); } /** * Returns the first header with a specified name of this message. * *

* If there is more than one matching header in the message the first element of {@link #getHeaders(String)} is returned. *

* This method always returns a value so that you can perform assertions on the result. * * @param name The name of the header to return. * @return The header, never null. */ @Override /* HttpMessage */ public ResponseHeader getFirstHeader(String name) { return new ResponseHeader(name, request, this, headers.getFirst(name).orElse(null)).parser(getPartParserSession()); } /** * Returns the last header with a specified name of this message. * *

* If there is more than one matching header in the message the last element of {@link #getHeaders(String)} is returned. *

* This method always returns a value so that you can perform assertions on the result. * * @param name The name of the header to return. * @return The header, never null. */ @Override /* HttpMessage */ public ResponseHeader getLastHeader(String name) { return new ResponseHeader(name, request, this, headers.getLast(name).orElse(null)).parser(getPartParserSession()); } /** * Returns the response header with the specified name. * *

* If more that one header with the given name exists the values will be combined with ", " as per RFC 2616 Section 4.2. * * @param name The name of the header to return. * @return The header, never null. */ public ResponseHeader getHeader(String name) { return new ResponseHeader(name, request, this, headers.get(name).orElse(null)).parser(getPartParserSession()); } /** * Returns all the headers of this message. * * Headers are ordered in the sequence they were sent over a connection. * * @return All the headers of this message. */ @Override /* HttpMessage */ public ResponseHeader[] getAllHeaders() { return headers.stream().map(x -> new ResponseHeader(x.getName(), request, this, x).parser(getPartParserSession())).toArray(ResponseHeader[]::new); } /** * Adds a header to this message. * * The header will be appended to the end of the list. * * @param header The header to append. */ @Override /* HttpMessage */ public void addHeader(Header header) { headers.append(header); } /** * Adds a header to this message. * * The header will be appended to the end of the list. * * @param name The name of the header. * @param value The value of the header. */ @Override /* HttpMessage */ public void addHeader(String name, String value) { headers.append(name, value); } /** * Overwrites the first header with the same name. * * The new header will be appended to the end of the list, if no header with the given name can be found. * * @param header The header to set. */ @Override /* HttpMessage */ public void setHeader(Header header) { headers.set(header); } /** * Overwrites the first header with the same name. * * The new header will be appended to the end of the list, if no header with the given name can be found. * * @param name The name of the header. * @param value The value of the header. */ @Override /* HttpMessage */ public void setHeader(String name, String value) { headers.set(name, value); } /** * Overwrites all the headers in the message. * * @param headers The array of headers to set. */ @Override /* HttpMessage */ public void setHeaders(Header[] headers) { this.headers = HeaderList.of(headers); } /** * Removes a header from this message. * * @param header The header to remove. */ @Override /* HttpMessage */ public void removeHeader(Header header) { headers.remove(header); } /** * Removes all headers with a certain name from this message. * * @param name The name of the headers to remove. */ @Override /* HttpMessage */ public void removeHeaders(String name) { headers.remove(name); } /** * Returns an iterator of all the headers. * * @return {@link Iterator} that returns {@link Header} objects in the sequence they are sent over a connection. */ @Override /* HttpMessage */ public HeaderIterator headerIterator() { return headers.headerIterator(); } /** * Returns an iterator of the headers with a given name. * * @param name The name of the headers over which to iterate, or null for all headers. * @return {@link Iterator} that returns {@link Header} objects with the argument name in the sequence they are sent over a connection. */ @Override /* HttpMessage */ public HeaderIterator headerIterator(String name) { return headers.headerIterator(name); } /** * Returns the parameters effective for this message as set by {@link #setParams(HttpParams)}. * * @return The parameters effective for this message as set by {@link #setParams(HttpParams)}. * @deprecated Use configuration classes provided org.apache.http.config and org.apache.http.client.config. */ @Override /* HttpMessage */ @Deprecated public HttpParams getParams() { return response.getParams(); } /** * Provides parameters to be used for the processing of this message. * * @param params The parameters. * @deprecated Use configuration classes provided org.apache.http.config and org.apache.http.client.config. */ @Override /* HttpMessage */ @Deprecated public void setParams(HttpParams params) { response.setParams(params); } void close() throws RestCallException { if (isClosed) return; isClosed = true; EntityUtils.consumeQuietly(response.getEntity()); if (!request.isLoggingSuppressed() && (request.isDebug() || client.logRequestsPredicate.test(request, this))) { if (client.logRequests == DetailLevel.SIMPLE) { client.log(client.logRequestsLevel, "HTTP {0} {1}, {2}", request.getMethod(), request.getURI(), this.getStatusLine()); } else if (request.isDebug() || client.logRequests == DetailLevel.FULL) { String output = getContent().asString(); StringBuilder sb = new StringBuilder(); sb.append("\n=== HTTP Call (outgoing) ======================================================"); sb.append("\n=== REQUEST ===\n"); sb.append(request.getMethod()).append(" ").append(request.getURI()); sb.append("\n---request headers---"); request.getHeaders().forEach(x -> sb.append("\n\t").append(x)); if (request.hasHttpEntity()) { sb.append("\n---request entity---"); HttpEntity e = request.getHttpEntity(); if (e.getContentType() != null) sb.append("\n\t").append(e.getContentType()); if (e.isRepeatable()) { try { sb.append("\n---request content---\n").append(EntityUtils.toString(e)); } catch (Exception ex) { sb.append("\n---request content exception---\n").append(ex.getMessage()); } } } sb.append("\n=== RESPONSE ===\n").append(getStatusLine()); sb.append("\n---response headers---"); for (Header h : getAllHeaders()) sb.append("\n\t").append(h); sb.append("\n---response content---\n").append(output); sb.append("\n=== END ======================================================================="); client.log(client.logRequestsLevel, sb.toString()); } } for (RestCallInterceptor r : request.interceptors) { try { r.onClose(request, this); } catch (RuntimeException | RestCallException e) { throw e; } catch (Exception e) { throw new RestCallException(this, e, "Interceptor throw exception on close"); } } client.onCallClose(request, this); } //------------------------------------------------------------------------------------------------------------------ // Other methods //------------------------------------------------------------------------------------------------------------------ /** * Creates a session of the specified part parser. * * @param parser The parser to create a session for. * @return A session of the specified parser. */ protected HttpPartParserSession getPartParserSession(HttpPartParser parser) { HttpPartParserSession s = partParserSessions.get(parser); if (s == null) { s = parser.getPartSession(); partParserSessions.put(parser, s); } return s; } /** * Creates a session of the client-default parat parser. * * @return A session of the specified parser. */ protected HttpPartParserSession getPartParserSession() { if (partParserSession == null) partParserSession = client.getPartParser().getPartSession(); return partParserSession; } HttpResponse asHttpResponse() { return response; } //----------------------------------------------------------------------------------------------------------------- // Fluent setters //----------------------------------------------------------------------------------------------------------------- // // }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy