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;
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;
public static OutboundJaxrsResponse from(Response response, Configuration configuration) {
if (response instanceof OutboundJaxrsResponse) {
((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);
}
}
@Deprecated
public static OutboundJaxrsResponse from(Response response) {
return from(response, (Configuration) null);
}
public OutboundJaxrsResponse(StatusType status, OutboundMessageContext context) {
this.status = status;
this.context = 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) {
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) {
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> () {
return context.getStringHeaders();
}
@Override
public String (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 + "}";
}
public static class Builder extends ResponseBuilder {
private StatusType status;
private final OutboundMessageContext context;
private static final InheritableThreadLocal<URI> baseUriThreadLocal = new InheritableThreadLocal<URI>();
public static void setBaseUri(URI baseUri) {
baseUriThreadLocal.set(baseUri);
}
private static URI getBaseUri() {
return baseUriThreadLocal.get();
}
public static void clearBaseUri() {
baseUriThreadLocal.remove();
}
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 (String name, Object value) {
return header(name, value, false);
}
private jakarta.ws.rs.core.Response.ResponseBuilder (String name, Object value) {
return header(name, value, true);
}
private jakarta.ws.rs.core.Response.ResponseBuilder (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;
}
}
}