org.glassfish.jersey.message.internal.ReaderInterceptorExecutor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaxrs-ri Show documentation
Show all versions of jaxrs-ri Show documentation
A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle
(jaxrs-ri.jar).
Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and
contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external
RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source
bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external
RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI
sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from
the command line.
/*
* 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.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.NoContentException;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.ReaderInterceptor;
import jakarta.ws.rs.ext.ReaderInterceptorContext;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.PropertiesDelegate;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
import org.glassfish.jersey.message.MessageBodyWorkers;
/**
* Represents reader interceptor chain executor for both client and server side.
* It constructs wrapped interceptor chain and invokes it. At the end of the chain
* a {@link MessageBodyReader message body reader} execution interceptor is inserted,
* which finally reads an entity from the output stream provided by the chain.
*
* @author Miroslav Fuksa
* @author Jakub Podlesak
*/
public final class ReaderInterceptorExecutor extends InterceptorExecutor
implements ReaderInterceptorContext, InjectionManagerSupplier {
private static final Logger LOGGER = Logger.getLogger(ReaderInterceptorExecutor.class.getName());
private final MultivaluedMap headers;
private final Iterator interceptors;
private final MessageBodyWorkers workers;
private final boolean translateNce;
private final InjectionManager injectionManager;
private InputStream inputStream;
private int processedCount;
/**
* Constructs a new executor to read given type from provided {@link InputStream entityStream}.
*
* @param rawType raw Java entity type.
* @param type generic Java entity type.
* @param annotations array of annotations on the declaration of the artifact
* that will be initialized with the produced instance. E.g. if the message
* body is to be converted into a method parameter, this will be the annotations
* on that parameter returned by {@code Method.getParameterAnnotations}.
* @param mediaType media type of the HTTP entity.
* @param headers mutable message headers.
* @param propertiesDelegate request-scoped properties delegate.
* @param inputStream entity input stream.
* @param workers {@link org.glassfish.jersey.message.MessageBodyWorkers Message body workers}.
* @param readerInterceptors Reader interceptor that are to be used to intercept the reading of an entity.
* The interceptors will be executed in the same order as given in this parameter.
* @param translateNce if {@code true}, the {@link jakarta.ws.rs.core.NoContentException} thrown by a selected message
* body
* reader will be translated into a {@link jakarta.ws.rs.BadRequestException} as required by
* @param injectionManager injection manager.
*/
ReaderInterceptorExecutor(final Class rawType, final Type type,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap headers,
final PropertiesDelegate propertiesDelegate,
final InputStream inputStream,
final MessageBodyWorkers workers,
final Iterable readerInterceptors,
final boolean translateNce,
final InjectionManager injectionManager) {
super(rawType, type, annotations, mediaType, propertiesDelegate);
this.headers = headers;
this.inputStream = inputStream;
this.workers = workers;
this.translateNce = translateNce;
this.injectionManager = injectionManager;
final List effectiveInterceptors = StreamSupport.stream(readerInterceptors.spliterator(), false)
.collect(Collectors.toList());
effectiveInterceptors.add(new TerminalReaderInterceptor());
this.interceptors = effectiveInterceptors.iterator();
this.processedCount = 0;
}
/**
* Starts the interceptor chain execution.
*
* @return an entity read from the stream.
*/
@Override
@SuppressWarnings("unchecked")
public Object proceed() throws IOException {
if (!interceptors.hasNext()) {
throw new ProcessingException(LocalizationMessages.ERROR_INTERCEPTOR_READER_PROCEED());
}
final ReaderInterceptor interceptor = interceptors.next();
traceBefore(interceptor, MsgTraceEvent.RI_BEFORE);
try {
return interceptor.aroundReadFrom(this);
} finally {
processedCount++;
traceAfter(interceptor, MsgTraceEvent.RI_AFTER);
}
}
@Override
public InputStream getInputStream() {
return this.inputStream;
}
@Override
public void setInputStream(final InputStream is) {
this.inputStream = is;
}
@Override
public MultivaluedMap getHeaders() {
return headers;
}
/**
* Get number of processed interceptors.
*
* @return number of processed interceptors.
*/
int getProcessedCount() {
return processedCount;
}
@Override
public InjectionManager getInjectionManager() {
return injectionManager;
}
/**
* Terminal reader interceptor which choose the appropriate {@link MessageBodyReader}
* and reads the entity from the input stream. The order of actions is the following:
* 1. choose the appropriate {@link MessageBodyReader}
* 3. reads the entity from the output stream
*/
private class TerminalReaderInterceptor implements ReaderInterceptor {
@Override
@SuppressWarnings("unchecked")
public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException {
processedCount--; //this is not regular interceptor -> count down
traceBefore(null, MsgTraceEvent.RI_BEFORE);
try {
final TracingLogger tracingLogger = getTracingLogger();
if (tracingLogger.isLogEnabled(MsgTraceEvent.MBR_FIND)) {
tracingLogger.log(MsgTraceEvent.MBR_FIND,
context.getType().getName(),
(context.getGenericType() instanceof Class
? ((Class) context.getGenericType()).getName() : context.getGenericType()),
String.valueOf(context.getMediaType()), java.util.Arrays.toString(context.getAnnotations()));
}
final MessageBodyReader bodyReader = workers.getMessageBodyReader(
context.getType(),
context.getGenericType(),
context.getAnnotations(),
context.getMediaType(),
ReaderInterceptorExecutor.this);
final EntityInputStream input = new EntityInputStream(context.getInputStream());
if (bodyReader == null) {
if (input.isEmpty() && !context.getHeaders().containsKey(HttpHeaders.CONTENT_TYPE)) {
return null;
} else {
LOGGER.log(Level.FINE, LocalizationMessages.ERROR_NOTFOUND_MESSAGEBODYREADER(context.getMediaType(),
context.getType(), context.getGenericType()));
throw new MessageBodyProviderNotFoundException(LocalizationMessages.ERROR_NOTFOUND_MESSAGEBODYREADER(
context.getMediaType(), context.getType(), context.getGenericType()));
}
}
Object entity = invokeReadFrom(context, bodyReader, input);
if (bodyReader instanceof CompletableReader) {
entity = ((CompletableReader) bodyReader).complete(entity);
}
return entity;
} finally {
clearLastTracedInterceptor();
traceAfter(null, MsgTraceEvent.RI_AFTER);
}
}
@SuppressWarnings("unchecked")
private Object invokeReadFrom(final ReaderInterceptorContext context, final MessageBodyReader reader,
final EntityInputStream input) throws WebApplicationException, IOException {
final TracingLogger tracingLogger = getTracingLogger();
final long timestamp = tracingLogger.timestamp(MsgTraceEvent.MBR_READ_FROM);
final InputStream stream = new UnCloseableInputStream(input, reader);
try {
return reader.readFrom(context.getType(), context.getGenericType(), context.getAnnotations(),
context.getMediaType(), context.getHeaders(), stream);
} catch (final NoContentException ex) {
if (translateNce) {
throw new BadRequestException(ex);
} else {
throw ex;
}
} finally {
tracingLogger.logDuration(MsgTraceEvent.MBR_READ_FROM, timestamp, reader);
}
}
}
/**
* {@link jakarta.ws.rs.ext.MessageBodyReader}s should not close the given {@link java.io.InputStream stream}. This input
* stream makes sure that the stream is not closed even if MBR tries to do it.
*/
private static class UnCloseableInputStream extends InputStream {
private final InputStream original;
private final MessageBodyReader reader;
private UnCloseableInputStream(final InputStream original, final MessageBodyReader reader) {
this.original = original;
this.reader = reader;
}
@Override
public int read() throws IOException {
return original.read();
}
@Override
public int read(final byte[] b) throws IOException {
return original.read(b);
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
return original.read(b, off, len);
}
@Override
public long skip(final long l) throws IOException {
return original.skip(l);
}
@Override
public int available() throws IOException {
return original.available();
}
@Override
public synchronized void mark(final int i) {
original.mark(i);
}
@Override
public synchronized void reset() throws IOException {
original.reset();
}
@Override
public boolean markSupported() {
return original.markSupported();
}
@Override
public void close() throws IOException {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, LocalizationMessages.MBR_TRYING_TO_CLOSE_STREAM(reader.getClass()));
}
}
private InputStream unwrap() {
return original;
}
}
/**
* Make the {@link InputStream} able to close.
*
* The purpose of this utility method is to undo effect of {@link ReaderInterceptorExecutor.UnCloseableInputStream}.
*
* @param inputStream Potential {@link ReaderInterceptorExecutor.UnCloseableInputStream} to undo its effect
* @return Input stream that is possible to close
*/
public static InputStream closeableInputStream(InputStream inputStream) {
if (inputStream instanceof UnCloseableInputStream) {
return ((UnCloseableInputStream) inputStream).unwrap();
} else {
return inputStream;
}
}
}