This is an automated email from the ASF dual-hosted git repository. dblevins pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomee-jakarta.git
commit 33415ca9cef1054d21f483f111a1adba6f7029b8 Author: David Blevins <[email protected]> AuthorDate: Fri Apr 30 01:56:37 2021 -0700 TOMEE-3187 jaxrs.spec.provider.standardnotnull clientDataSourceProviderTest --- .../org/apache/cxf/jaxrs/impl/ResponseImpl.java | 629 +++++++++++++++++++++ .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java | 69 +-- 2 files changed, 664 insertions(+), 34 deletions(-) diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java new file mode 100644 index 0000000..e240f50 --- /dev/null +++ b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java @@ -0,0 +1,629 @@ +/** + * 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); + } +} \ No newline at end of file diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/transform/src/patch/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java index 0254368..4cf99ff 100644 --- a/transform/src/patch/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java +++ b/transform/src/patch/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java @@ -47,39 +47,40 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import jakarta.ws.rs.ClientErrorException; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.HttpMethod; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.container.AsyncResponse; -import jakarta.ws.rs.container.ContainerRequestContext; -import jakarta.ws.rs.container.ContainerRequestFilter; -import jakarta.ws.rs.container.ContainerResponseContext; -import jakarta.ws.rs.container.ContainerResponseFilter; -import jakarta.ws.rs.container.ResourceContext; -import jakarta.ws.rs.container.ResourceInfo; -import jakarta.ws.rs.core.Application; -import jakarta.ws.rs.core.Configuration; -import jakarta.ws.rs.core.Cookie; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.core.PathSegment; -import jakarta.ws.rs.core.Request; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.ResponseBuilder; -import jakarta.ws.rs.core.SecurityContext; -import jakarta.ws.rs.core.StreamingOutput; -import jakarta.ws.rs.core.UriInfo; -import jakarta.ws.rs.ext.ContextResolver; -import jakarta.ws.rs.ext.MessageBodyReader; -import jakarta.ws.rs.ext.MessageBodyWriter; -import jakarta.ws.rs.ext.Providers; -import jakarta.ws.rs.ext.ReaderInterceptor; -import jakarta.ws.rs.ext.ReaderInterceptorContext; -import jakarta.ws.rs.ext.WriterInterceptor; -import jakarta.ws.rs.ext.WriterInterceptorContext; +import javax.activation.DataSource; +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.Consumes; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.container.ResourceContext; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Configuration; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.PathSegment; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.StreamingOutput; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Providers; +import javax.ws.rs.ext.ReaderInterceptor; +import javax.ws.rs.ext.ReaderInterceptorContext; +import javax.ws.rs.ext.WriterInterceptor; +import javax.ws.rs.ext.WriterInterceptorContext; import javax.xml.namespace.QName; import org.apache.cxf.common.i18n.BundleUtils; @@ -168,7 +169,7 @@ public final class JAXRSUtils { private static final String HTTP_CHARSET_PARAM = "charset"; private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0]; private static final Set<Class<?>> STREAMING_OUT_TYPES = new HashSet<>( - Arrays.asList(InputStream.class, Reader.class, StreamingOutput.class)); + Arrays.asList(InputStream.class, Reader.class, StreamingOutput.class, DataSource.class)); private JAXRSUtils() { }
