/*
 * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.message.internal;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.EntityTag;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Link;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.NewCookie;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Variant;

import org.glassfish.jersey.internal.LocalizationMessages;

An outbound JAX-RS response message. The implementation delegates method calls to an underlying outbound message context.
Author:Marek Potociar
/** * An outbound JAX-RS response message. * * The implementation delegates method calls to an {@link #getContext() underlying * outbound message context}. * * @author Marek Potociar */
public class OutboundJaxrsResponse extends jakarta.ws.rs.core.Response { private final OutboundMessageContext context; private final StatusType status; private boolean closed = false; private boolean buffered = false;
Get an OutboundJaxrsResponse instance for a given JAX-RS response.
Params:
  • response – response instance to from.
  • configuration – the related client/server side Configuration. Can be null.
Returns:corresponding OutboundJaxrsResponse instance.
/** * Get an OutboundJaxrsResponse instance for a given JAX-RS response. * * @param response response instance to from. * @param configuration the related client/server side {@link Configuration}. Can be {@code null}. * @return corresponding {@code OutboundJaxrsResponse} instance. */
public static OutboundJaxrsResponse from(Response response, Configuration configuration) { if (response instanceof OutboundJaxrsResponse) { // RuntimeDelegate.getInstance().createResponseBuilder() does not set configuration ((OutboundJaxrsResponse) response).context.setConfiguration(configuration); return (OutboundJaxrsResponse) response; } else { final StatusType status = response.getStatusInfo(); final OutboundMessageContext context = new OutboundMessageContext(configuration); context.getHeaders().putAll(response.getMetadata()); context.setEntity(response.getEntity()); return new OutboundJaxrsResponse(status, context); } }
Get an OutboundJaxrsResponse instance for a given JAX-RS response.
Params:
  • response – response instance to from.
See Also:
Returns:corresponding OutboundJaxrsResponse instance.
/** * Get an OutboundJaxrsResponse instance for a given JAX-RS response. * * @param response response instance to from. * @return corresponding {@code OutboundJaxrsResponse} instance. * @see #from(Response, Configuration) */
@Deprecated public static OutboundJaxrsResponse from(Response response) { return from(response, (Configuration) null); }
Create new outbound JAX-RS response message instance.
Params:
  • status – response status.
  • context – underlying outbound message context.
/** * Create new outbound JAX-RS response message instance. * * @param status response status. * @param context underlying outbound message context. */
public OutboundJaxrsResponse(StatusType status, OutboundMessageContext context) { this.status = status; this.context = context; }
Get the underlying outbound message context.
Returns:underlying outbound message context.
/** * Get the underlying outbound message context. * * @return underlying outbound message context. */
public OutboundMessageContext getContext() { return context; } @Override public int getStatus() { return status.getStatusCode(); } @Override public StatusType getStatusInfo() { return status; } @Override public Object getEntity() { if (closed) { throw new IllegalStateException(LocalizationMessages.RESPONSE_CLOSED()); } return context.getEntity(); } @Override public <T> T readEntity(Class<T> type) throws ProcessingException { throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED_ON_OUTBOUND_MESSAGE()); } @Override public <T> T readEntity(GenericType<T> entityType) throws ProcessingException { throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED_ON_OUTBOUND_MESSAGE()); } @Override public <T> T readEntity(Class<T> type, Annotation[] annotations) throws ProcessingException { throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED_ON_OUTBOUND_MESSAGE()); } @Override public <T> T readEntity(GenericType<T> entityType, Annotation[] annotations) throws ProcessingException { throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED_ON_OUTBOUND_MESSAGE()); } @Override public boolean hasEntity() { if (closed) { throw new IllegalStateException(LocalizationMessages.RESPONSE_CLOSED()); } return context.hasEntity(); } @Override public boolean bufferEntity() throws ProcessingException { if (closed) { throw new IllegalStateException(LocalizationMessages.RESPONSE_CLOSED()); } if (!context.hasEntity() || !InputStream.class.isAssignableFrom(context.getEntityClass())) { return false; } if (buffered) { // already buffered return true; } final InputStream in = InputStream.class.cast(context.getEntity()); final ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; try { while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } catch (IOException ex) { throw new ProcessingException(ex); } finally { try { in.close(); } catch (IOException ex) { throw new ProcessingException(ex); } } context.setEntity(new ByteArrayInputStream(out.toByteArray())); buffered = true; return true; } @Override public void close() throws ProcessingException { closed = true; context.close(); if (buffered) { // release buffer context.setEntity(null); } else if (context.hasEntity() && InputStream.class.isAssignableFrom(context.getEntityClass())) { try { InputStream.class.cast(context.getEntity()).close(); } catch (IOException ex) { throw new ProcessingException(ex); } } } @Override public MultivaluedMap<String, String> getStringHeaders() { return context.getStringHeaders(); } @Override public String getHeaderString(String name) { return context.getHeaderString(name); } @Override public MediaType getMediaType() { return context.getMediaType(); } @Override public Locale getLanguage() { return context.getLanguage(); } @Override public int getLength() { return context.getLength(); } @Override public Map<String, NewCookie> getCookies() { return context.getResponseCookies(); } @Override public EntityTag getEntityTag() { return context.getEntityTag(); } @Override public Date getDate() { return context.getDate(); } @Override public Date getLastModified() { return context.getLastModified(); } @Override public Set<String> getAllowedMethods() { return context.getAllowedMethods(); } @Override public URI getLocation() { return context.getLocation(); } @Override public Set<Link> getLinks() { return context.getLinks(); } @Override public boolean hasLink(String relation) { return context.hasLink(relation); } @Override public Link getLink(String relation) { return context.getLink(relation); } @Override public Link.Builder getLinkBuilder(String relation) { return context.getLinkBuilder(relation); } @Override @SuppressWarnings("unchecked") public MultivaluedMap<String, Object> getMetadata() { return context.getHeaders(); } @Override public String toString() { return "OutboundJaxrsResponse{" + "status=" + status.getStatusCode() + ", reason=" + status.getReasonPhrase() + ", hasEntity=" + context.hasEntity() + ", closed=" + closed + ", buffered=" + buffered + "}"; }
Outbound JAX-RS Response.ResponseBuilder implementation. The implementation delegates method calls to an underlying outbound message context. Upon a call to a build() method a new instance of OutboundJaxrsResponse is produced.
/** * Outbound JAX-RS {@code Response.ResponseBuilder} implementation. * * The implementation delegates method calls to an {@link #getContext() underlying * outbound message context}. Upon a call to a {@link #build()} method * a new instance of {@link OutboundJaxrsResponse} is produced. */
public static class Builder extends ResponseBuilder { private StatusType status; private final OutboundMessageContext context; /* thread-local storage for request baseUri for use in the response headers */ private static final InheritableThreadLocal<URI> baseUriThreadLocal = new InheritableThreadLocal<URI>();
Set the baseUri of the actual request into the InheritableThreadLocal.

The baseUri will be used for absolutizing the location header content in case that only a relative URI is provided.

After resource method invocation when the value is not needed any more to be stored in ThreadLocal clearBaseUri() should be called for cleanup in order to prevent possible memory leaks.

Params:
  • baseUri – - baseUri of the actual request
See Also:
Since:2.4
/** * Set the {@code baseUri} of the actual request into the {@link InheritableThreadLocal}. * <p> * The {@code baseUri} will be used for absolutizing the location header * content in case that only a relative URI is provided. * </p> * <p> * After resource method invocation when the value is not needed * any more to be stored in {@code ThreadLocal} {@link #clearBaseUri() clearBaseUri()} should be * called for cleanup in order to prevent possible memory leaks. * </p> * * @param baseUri - baseUri of the actual request * @see #location(java.net.URI) * @since 2.4 */
public static void setBaseUri(URI baseUri) { baseUriThreadLocal.set(baseUri); }
Return request baseUri previously set by setBaseUri(URI). Returned URI is used for absolutization of the location header in case that only a relative URI was provided.
See Also:
Returns:baseUri of the actual request
Since:2.4
/** * Return request baseUri previously set by {@link #setBaseUri(java.net.URI)}. * * Returned {@link URI} is used for absolutization of the location header in case that only a relative * {@code URI} was provided. * * @return baseUri of the actual request * @see #location(java.net.URI) * @since 2.4 */
private static URI getBaseUri() { return baseUriThreadLocal.get(); }
Remove the current thread's value for baseUri thread-local variable (set by setBaseUri(URI)). Should be called after resource method invocation for cleanup.
See Also:
Since:2.4
/** * Remove the current thread's value for baseUri thread-local variable (set by {@link #setBaseUri(java.net.URI)}). * * Should be called after resource method invocation for cleanup. * * @see #location(java.net.URI) * @since 2.4 */
public static void clearBaseUri() { baseUriThreadLocal.remove(); }
Create new outbound JAX-RS response builder.
Params:
  • context – underlying outbound message context.
/** * Create new outbound JAX-RS response builder. * * @param context underlying outbound message context. */
public Builder(final OutboundMessageContext context) { this.context = context; } @Override public jakarta.ws.rs.core.Response build() { StatusType st = status; if (st == null) { st = context.hasEntity() ? Status.OK : Status.NO_CONTENT; } return new OutboundJaxrsResponse(st, new OutboundMessageContext(context)); } @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"}) @Override public ResponseBuilder clone() { return new Builder(new OutboundMessageContext(context)).status(status); } @Override public jakarta.ws.rs.core.Response.ResponseBuilder status(StatusType status) { if (status == null) { throw new IllegalArgumentException("Response status must not be 'null'"); } this.status = status; return this; } @Override public ResponseBuilder status(int status, final String reasonPhrase) { if (status < 100 || status > 599) { throw new IllegalArgumentException("Response status must not be less than '100' or greater than '599'"); } final Status.Family family = Status.Family.familyOf(status); this.status = new StatusType() { @Override public int getStatusCode() { return status; } @Override public Status.Family getFamily() { return family; } @Override public String getReasonPhrase() { return reasonPhrase; } }; return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder status(int code) { this.status = Statuses.from(code); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder entity(Object entity) { context.setEntity(entity); return this; } @Override public ResponseBuilder entity(Object entity, Annotation[] annotations) { context.setEntity(entity, annotations); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder type(MediaType type) { context.setMediaType(type); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder type(String type) { return type(type == null ? null : MediaType.valueOf(type)); } @Override public jakarta.ws.rs.core.Response.ResponseBuilder variant(Variant variant) { if (variant == null) { type((MediaType) null); language((String) null); encoding(null); return this; } type(variant.getMediaType()); language(variant.getLanguage()); encoding(variant.getEncoding()); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder variants(List<Variant> variants) { if (variants == null) { header(HttpHeaders.VARY, null); return this; } if (variants.isEmpty()) { return this; } MediaType accept = variants.get(0).getMediaType(); boolean vAccept = false; Locale acceptLanguage = variants.get(0).getLanguage(); boolean vAcceptLanguage = false; String acceptEncoding = variants.get(0).getEncoding(); boolean vAcceptEncoding = false; for (Variant v : variants) { vAccept |= !vAccept && vary(v.getMediaType(), accept); vAcceptLanguage |= !vAcceptLanguage && vary(v.getLanguage(), acceptLanguage); vAcceptEncoding |= !vAcceptEncoding && vary(v.getEncoding(), acceptEncoding); } StringBuilder vary = new StringBuilder(); append(vary, vAccept, HttpHeaders.ACCEPT); append(vary, vAcceptLanguage, HttpHeaders.ACCEPT_LANGUAGE); append(vary, vAcceptEncoding, HttpHeaders.ACCEPT_ENCODING); if (vary.length() > 0) { header(HttpHeaders.VARY, vary.toString()); } return this; } private boolean vary(MediaType v, MediaType vary) { return v != null && !v.equals(vary); } private boolean vary(Locale v, Locale vary) { return v != null && !v.equals(vary); } private boolean vary(String v, String vary) { return v != null && !v.equalsIgnoreCase(vary); } private void append(StringBuilder sb, boolean v, String s) { if (v) { if (sb.length() > 0) { sb.append(','); } sb.append(s); } } @Override public jakarta.ws.rs.core.Response.ResponseBuilder language(String language) { headerSingle(HttpHeaders.CONTENT_LANGUAGE, language); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder language(Locale language) { headerSingle(HttpHeaders.CONTENT_LANGUAGE, language); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder location(URI location) { URI locationUri = location; if (location != null && !location.isAbsolute()) { URI baseUri = getBaseUri(); if (baseUri != null) { locationUri = baseUri.resolve(location); } } headerSingle(HttpHeaders.LOCATION, locationUri); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder contentLocation(URI location) { headerSingle(HttpHeaders.CONTENT_LOCATION, location); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder encoding(String encoding) { headerSingle(HttpHeaders.CONTENT_ENCODING, encoding); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder tag(EntityTag tag) { headerSingle(HttpHeaders.ETAG, tag); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder tag(String tag) { return tag(tag == null ? null : new EntityTag(tag)); } @Override public jakarta.ws.rs.core.Response.ResponseBuilder lastModified(Date lastModified) { headerSingle(HttpHeaders.LAST_MODIFIED, lastModified); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder cacheControl(CacheControl cacheControl) { headerSingle(HttpHeaders.CACHE_CONTROL, cacheControl); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder expires(Date expires) { headerSingle(HttpHeaders.EXPIRES, expires); return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder cookie(NewCookie... cookies) { if (cookies != null) { for (NewCookie cookie : cookies) { header(HttpHeaders.SET_COOKIE, cookie); } } else { header(HttpHeaders.SET_COOKIE, null); } return this; } @Override public jakarta.ws.rs.core.Response.ResponseBuilder header(String name, Object value) { return header(name, value, false); } private jakarta.ws.rs.core.Response.ResponseBuilder headerSingle(String name, Object value) { return header(name, value, true); } private jakarta.ws.rs.core.Response.ResponseBuilder header(String name, Object value, boolean single) { if (value != null) { if (single) { context.getHeaders().putSingle(name, value); } else { context.getHeaders().add(name, value); } } else { context.getHeaders().remove(name); } return this; } @Override public ResponseBuilder variants(Variant... variants) { return variants(Arrays.asList(variants)); } @Override public ResponseBuilder links(Link... links) { if (links != null) { for (Link link : links) { header(HttpHeaders.LINK, link); } } else { header(HttpHeaders.LINK, null); } return this; } @Override public ResponseBuilder link(URI uri, String rel) { header(HttpHeaders.LINK, Link.fromUri(uri).rel(rel).build()); return this; } @Override public ResponseBuilder link(String uri, String rel) { header(HttpHeaders.LINK, Link.fromUri(uri).rel(rel).build()); return this; } @Override public ResponseBuilder allow(String... methods) { if (methods == null || (methods.length == 1 && methods[0] == null)) { return allow((Set<String>) null); } else { return allow(new HashSet<String>(Arrays.asList(methods))); } } @Override public ResponseBuilder allow(Set<String> methods) { if (methods == null) { return header(HttpHeaders.ALLOW, null, true); } StringBuilder allow = new StringBuilder(); for (String m : methods) { append(allow, true, m); } return header(HttpHeaders.ALLOW, allow, true); } @Override public ResponseBuilder replaceAll(MultivaluedMap<String, Object> headers) { context.replaceHeaders(headers); return this; } } }