blob: e240f50e2966a7e2cb2f3dc1b51020a18eb80472 [file] [log] [blame]
/**
* 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.cxf.jaxrs.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.ResponseProcessingException;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.NoContentException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.ReaderInputStream;
import org.apache.cxf.jaxrs.provider.ProviderFactory;
import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.jaxrs.utils.InjectionUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;
public final class ResponseImpl extends Response {
private static final Pattern LINK_DELIMITER = Pattern.compile(",\\s*(?=\\<|$)");
private StatusType status;
private Object entity;
private Annotation[] entityAnnotations;
private MultivaluedMap<String, Object> metadata;
private Message outMessage;
private boolean entityClosed;
private boolean entityBufferred;
private Object lastEntity;
ResponseImpl(int statusCode) {
this.status = createStatusType(statusCode, null);
}
ResponseImpl(int statusCode, Object entity) {
this(statusCode);
this.entity = entity;
}
ResponseImpl(int statusCode, Object entity, String reasonPhrase) {
this.status = createStatusType(statusCode, reasonPhrase);
this.entity = entity;
}
public void addMetadata(MultivaluedMap<String, Object> meta) {
this.metadata = meta;
}
public void setStatus(int statusCode) {
this.status = createStatusType(statusCode, null);
}
public void setStatus(int statusCode, String reasonPhrase) {
this.status = createStatusType(statusCode, reasonPhrase);
}
public void setEntity(Object e, Annotation[] anns) {
this.entity = e;
this.entityAnnotations = anns;
}
public void setEntityAnnotations(Annotation[] anns) {
this.entityAnnotations = anns;
}
public Annotation[] getEntityAnnotations() {
return entityAnnotations;
}
public void setOutMessage(Message message) {
this.outMessage = message;
}
public Message getOutMessage() {
return this.outMessage;
}
@Override
public int getStatus() {
return status.getStatusCode();
}
@Override
public StatusType getStatusInfo() {
return status;
}
public Object getActualEntity() {
checkEntityIsClosed();
return lastEntity != null ? lastEntity : entity;
}
@Override
public Object getEntity() {
return InjectionUtils.getEntity(getActualEntity());
}
@Override
public boolean hasEntity() {
// per spec, need to check if the stream exists and if it has data.
Object actualEntity = getActualEntity();
if (actualEntity == null) {
return false;
} else if (actualEntity instanceof InputStream) {
final InputStream is = (InputStream) actualEntity;
try {
if (is.markSupported()) {
is.mark(1);
int i = is.read();
is.reset();
return i != -1;
} else {
try {
if (is.available() > 0) {
return true;
}
} catch (IOException ioe) {
//Do nothing
}
int b = is.read();
if (b == -1) {
return false;
}
PushbackInputStream pbis;
if (is instanceof PushbackInputStream) {
pbis = (PushbackInputStream) is;
} else {
pbis = new PushbackInputStream(is, 1);
if (lastEntity != null) {
lastEntity = pbis;
} else {
entity = pbis;
}
}
pbis.unread(b);
return true;
}
} catch (IOException ex) {
throw new ProcessingException(ex);
}
}
return true;
}
@Override
public MultivaluedMap<String, Object> getMetadata() {
return getHeaders();
}
@Override
public MultivaluedMap<String, Object> getHeaders() {
return metadata;
}
@Override
public MultivaluedMap<String, String> getStringHeaders() {
MetadataMap<String, String> headers = new MetadataMap<>(metadata.size());
for (Map.Entry<String, List<Object>> entry : metadata.entrySet()) {
String headerName = entry.getKey();
headers.put(headerName, toListOfStrings(entry.getValue()));
}
return headers;
}
@Override
public String getHeaderString(String header) {
List<Object> methodValues = metadata.get(header);
return HttpUtils.getHeaderString(toListOfStrings(methodValues));
}
// This conversion is needed as some values may not be Strings
private List<String> toListOfStrings(List<Object> values) {
if (values == null) {
return null;
}
List<String> stringValues = new ArrayList<>(values.size());
HeaderDelegate<Object> hd = HttpUtils.getHeaderDelegate(values.get(0));
for (Object value : values) {
String actualValue = hd == null ? value.toString() : hd.toString(value);
stringValues.add(actualValue);
}
return stringValues;
}
@Override
public Set<String> getAllowedMethods() {
List<Object> methodValues = metadata.get(HttpHeaders.ALLOW);
if (methodValues == null) {
return Collections.emptySet();
}
Set<String> methods = new HashSet<>();
for (Object o : methodValues) {
methods.add(o.toString());
}
return methods;
}
@Override
public Map<String, NewCookie> getCookies() {
List<Object> cookieValues = metadata.get(HttpHeaders.SET_COOKIE);
if (cookieValues == null) {
return Collections.emptyMap();
}
Map<String, NewCookie> cookies = new HashMap<>();
for (Object o : cookieValues) {
NewCookie newCookie = NewCookie.valueOf(o.toString());
cookies.put(newCookie.getName(), newCookie);
}
return cookies;
}
@Override
public Date getDate() {
return doGetDate(HttpHeaders.DATE);
}
private Date doGetDate(String dateHeader) {
Object value = metadata.getFirst(dateHeader);
return value == null || value instanceof Date ? (Date)value
: HttpUtils.getHttpDate(value.toString());
}
@Override
public EntityTag getEntityTag() {
Object header = metadata.getFirst(HttpHeaders.ETAG);
return header == null || header instanceof EntityTag ? (EntityTag)header
: EntityTag.valueOf(header.toString());
}
@Override
public Locale getLanguage() {
Object header = metadata.getFirst(HttpHeaders.CONTENT_LANGUAGE);
return header == null || header instanceof Locale ? (Locale)header
: HttpUtils.getLocale(header.toString());
}
@Override
public Date getLastModified() {
return doGetDate(HttpHeaders.LAST_MODIFIED);
}
@Override
public int getLength() {
Object header = metadata.getFirst(HttpHeaders.CONTENT_LENGTH);
return HttpUtils.getContentLength(header == null ? null : header.toString());
}
@Override
public URI getLocation() {
Object header = metadata.getFirst(HttpHeaders.LOCATION);
return header == null || header instanceof URI ? (URI)header
: URI.create(header.toString());
}
@Override
public MediaType getMediaType() {
Object header = metadata.getFirst(HttpHeaders.CONTENT_TYPE);
return header == null || header instanceof MediaType ? (MediaType)header
: (MediaType)JAXRSUtils.toMediaType(header.toString());
}
@Override
public boolean hasLink(String relation) {
List<Object> linkValues = metadata.get(HttpHeaders.LINK);
if (linkValues != null) {
for (Object o : linkValues) {
if (o instanceof Link && relation.equals(((Link)o).getRel())) {
return true;
}
String[] links = LINK_DELIMITER.split(o.toString());
for (String splitLink : links) {
Link link = Link.valueOf(splitLink);
if (relation.equals(link.getRel())) {
return true;
}
}
}
}
return false;
}
@Override
public Link getLink(String relation) {
Set<Link> links = getAllLinks();
for (Link link : links) {
if (link.getRel() != null && link.getRel().equals(relation)) {
return link;
}
}
return null;
}
@Override
public Link.Builder getLinkBuilder(String relation) {
Link link = getLink(relation);
return link == null ? null : Link.fromLink(link);
}
@Override
public Set<Link> getLinks() {
return new HashSet<>(getAllLinks());
}
private Set<Link> getAllLinks() {
List<Object> linkValues = metadata.get(HttpHeaders.LINK);
if (linkValues == null) {
return Collections.emptySet();
}
Set<Link> links = new LinkedHashSet<>();
for (Object o : linkValues) {
List<Link> parsedLinks = parseLink(o);
links.addAll(parsedLinks);
}
return links;
}
private Link makeAbsoluteLink(Link link) {
if (!link.getUri().isAbsolute()) {
URI requestURI = URI.create((String)outMessage.get(Message.REQUEST_URI));
link = Link.fromLink(link).baseUri(requestURI).build();
}
return link;
}
private List<Link> parseLink(Object o) {
if (o instanceof Link) {
return Collections.singletonList(makeAbsoluteLink((Link) o));
}
List<Link> links = new ArrayList<>();
String[] linkArray = LINK_DELIMITER.split(o.toString());
for (String textLink : linkArray) {
Link link = Link.valueOf(textLink);
links.add(makeAbsoluteLink(link));
}
return Collections.unmodifiableList(links);
}
@Override
public <T> T readEntity(Class<T> cls) throws ProcessingException, IllegalStateException {
return readEntity(cls, new Annotation[]{});
}
@Override
public <T> T readEntity(GenericType<T> genType) throws ProcessingException, IllegalStateException {
return readEntity(genType, new Annotation[]{});
}
@Override
public <T> T readEntity(Class<T> cls, Annotation[] anns) throws ProcessingException, IllegalStateException {
return doReadEntity(cls, cls, anns);
}
@Override
@SuppressWarnings("unchecked")
public <T> T readEntity(GenericType<T> genType, Annotation[] anns)
throws ProcessingException, IllegalStateException {
return doReadEntity((Class<T>) genType.getRawType(),
genType.getType(), anns);
}
public <T> T doReadEntity(Class<T> cls, Type t, Annotation[] anns)
throws ProcessingException, IllegalStateException {
checkEntityIsClosed();
//according to javadoc, should close when is not buffered.
boolean shouldClose = !entityBufferred && !JAXRSUtils.isStreamingOutType(cls);
if (lastEntity != null && cls.isAssignableFrom(lastEntity.getClass())
&& !(lastEntity instanceof InputStream)) {
return cls.cast(lastEntity);
}
MediaType mediaType = getMediaType();
if (mediaType == null) {
mediaType = MediaType.WILDCARD_TYPE;
}
// the stream is available if entity is IS or
// message contains XMLStreamReader or Reader
boolean entityStreamAvailable = entityStreamAvailable();
InputStream entityStream = null;
if (!entityStreamAvailable) {
// try create a stream if the entity is String or Number
entityStream = convertEntityToStreamIfPossible();
entityStreamAvailable = entityStream != null;
} else if (entity instanceof InputStream) {
entityStream = InputStream.class.cast(entity);
} else {
Message inMessage = getResponseMessage();
Reader reader = inMessage.getContent(Reader.class);
if (reader != null) {
entityStream = InputStream.class.cast(new ReaderInputStream(reader));
}
}
// we need to check for readers even if no IS is set - the readers may still do it
List<ReaderInterceptor> readers = outMessage == null ? null : ProviderFactory.getInstance(outMessage)
.createMessageBodyReaderInterceptor(cls, t, anns, mediaType, outMessage, entityStreamAvailable, null);
if (readers != null) {
try {
if (entityBufferred) {
InputStream.class.cast(entity).reset();
}
Message responseMessage = getResponseMessage();
responseMessage.put(Message.PROTOCOL_HEADERS, getHeaders());
lastEntity = JAXRSUtils.readFromMessageBodyReader(readers, cls, t,
anns,
entityStream,
mediaType,
responseMessage);
// close the entity after readEntity is called.
T tCastLastEntity = castLastEntity();
shouldClose = shouldClose && !(tCastLastEntity instanceof AutoCloseable)
&& !(tCastLastEntity instanceof Source);
if (shouldClose) {
close();
}
return tCastLastEntity;
} catch (NoContentException ex) {
//when content is empty, return null instead of throw exception to pass TCK
//check if basic type. Basic type throw exception, else return null.
if (isBasicType(cls)) {
autoClose(cls, true);
reportMessageHandlerProblem("MSG_READER_PROBLEM", cls, mediaType, ex);
} else {
if (shouldClose) {
close();
}
return null;
}
} catch (Exception ex) {
autoClose(cls, true);
reportMessageHandlerProblem("MSG_READER_PROBLEM", cls, mediaType, ex);
} finally {
ProviderFactory pf = ProviderFactory.getInstance(outMessage);
if (pf != null) {
pf.clearThreadLocalProxies();
}
}
} else if (entity != null && cls.isAssignableFrom(entity.getClass())) {
lastEntity = entity;
return castLastEntity();
} else if (entityStreamAvailable) {
reportMessageHandlerProblem("NO_MSG_READER", cls, mediaType, null);
}
throw new IllegalStateException("The entity is not backed by an input stream, entity class is : "
+ (entity != null ? entity.getClass().getName() : cls.getName()));
}
@SuppressWarnings("unchecked")
private <T> T castLastEntity() {
return (T)lastEntity;
}
public InputStream convertEntityToStreamIfPossible() {
String stringEntity = null;
if (entity instanceof String || entity instanceof Number) {
stringEntity = entity.toString();
}
if (stringEntity != null) {
try {
return new ByteArrayInputStream(stringEntity.getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {
throw new ProcessingException(ex);
}
}
return null;
}
private boolean entityStreamAvailable() {
if (entity == null) {
Message inMessage = getResponseMessage();
return inMessage != null && (inMessage.getContent(XMLStreamReader.class) != null
|| inMessage.getContent(Reader.class) != null);
}
return entity instanceof InputStream;
}
private Message getResponseMessage() {
Message responseMessage = outMessage.getExchange().getInMessage();
if (responseMessage == null) {
responseMessage = outMessage.getExchange().getInFaultMessage();
}
return responseMessage;
}
private void reportMessageHandlerProblem(String name, Class<?> cls, MediaType ct, Throwable cause) {
String errorMessage = JAXRSUtils.logMessageHandlerProblem(name, cls, ct);
throw new ResponseProcessingException(this, errorMessage, cause);
}
protected void autoClose(Class<?> cls, boolean exception) {
if (!entityBufferred && !JAXRSUtils.isStreamingOutType(cls)
&& (exception || MessageUtils.getContextualBoolean(outMessage, "response.stream.auto.close"))) {
close();
}
}
@Override
public boolean bufferEntity() throws ProcessingException {
checkEntityIsClosed();
if (!entityBufferred && entity instanceof InputStream) {
try {
InputStream oldEntity = (InputStream)entity;
entity = IOUtils.loadIntoBAIS(oldEntity);
oldEntity.close();
entityBufferred = true;
} catch (IOException ex) {
throw new ResponseProcessingException(this, ex);
}
}
return entityBufferred;
}
@Override
public void close() throws ProcessingException {
if (!entityClosed) {
if (!entityBufferred && entity instanceof InputStream) {
try {
((InputStream)entity).close();
} catch (IOException ex) {
throw new ResponseProcessingException(this, ex);
}
}
entity = null;
entityClosed = true;
}
}
private void checkEntityIsClosed() {
if (entityClosed) {
throw new IllegalStateException("Entity is not available");
}
}
private Response.StatusType createStatusType(int statusCode, String reasonPhrase) {
return new Response.StatusType() {
@Override
public Family getFamily() {
return Response.Status.Family.familyOf(statusCode);
}
@Override
public String getReasonPhrase() {
if (reasonPhrase != null) {
return reasonPhrase;
}
Response.Status statusEnum = Response.Status.fromStatusCode(statusCode);
return statusEnum != null ? statusEnum.getReasonPhrase() : "";
}
@Override
public int getStatusCode() {
return statusCode;
}
};
}
private static boolean isBasicType(Class<?> type) {
return type.isPrimitive() || Number.class.isAssignableFrom(type) || Boolean.class.isAssignableFrom(type)
|| Character.class.isAssignableFrom(type);
}
}