Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit 16db8dd

Browse files
pavelbucekGerrit Code Review
authored andcommitted
Merge "Updated fix for JRFCAF-1344."
2 parents 2950046 + 2e8d10b commit 16db8dd

File tree

9 files changed

+470
-245
lines changed

9 files changed

+470
-245
lines changed

core-common/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@
200200
<artifactId>osgi-resource-locator</artifactId>
201201
</dependency>
202202

203+
<dependency>
204+
<!-- Must be declared before JUnit dependency, otherwise not visible to JUnit. -->
205+
<groupId>org.jmockit</groupId>
206+
<artifactId>jmockit</artifactId>
207+
</dependency>
203208
<dependency>
204209
<groupId>junit</groupId>
205210
<artifactId>junit</artifactId>

core-common/src/main/java/org/glassfish/jersey/message/internal/EntityInputStream.java

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@
4242
import java.io.IOException;
4343
import java.io.InputStream;
4444
import java.io.PushbackInputStream;
45-
import java.util.concurrent.atomic.AtomicBoolean;
46-
import java.util.logging.Level;
47-
import java.util.logging.Logger;
4845

4946
import javax.ws.rs.ProcessingException;
5047

@@ -60,14 +57,13 @@
6057
* @author Marek Potociar (marek.potociar at oracle.com)
6158
*/
6259
public class EntityInputStream extends InputStream {
63-
private static final Logger LOGGER = Logger.getLogger(EntityInputStream.class.getName());
6460

65-
private volatile InputStream input;
66-
private final AtomicBoolean closed = new AtomicBoolean(false);
61+
private InputStream input;
62+
private boolean closed = false;
6763

6864
/**
6965
* Create an entity input stream instance wrapping the original input stream.
70-
*
66+
* <p/>
7167
* In case the original entity stream is already of type {@code EntityInputStream},
7268
* the stream is returned without wrapping.
7369
*
@@ -133,8 +129,7 @@ public boolean markSupported() {
133129
* a runtime {@link javax.ws.rs.ProcessingException} is thrown.
134130
* </p>
135131
*
136-
* @throws javax.ws.rs.ProcessingException
137-
* in case the reset operation on the underlying entity input stream failed.
132+
* @throws javax.ws.rs.ProcessingException in case the reset operation on the underlying entity input stream failed.
138133
*/
139134
@Override
140135
public void reset() {
@@ -158,15 +153,14 @@ public void close() throws ProcessingException {
158153
if (in == null) {
159154
return;
160155
}
161-
if (closed.compareAndSet(false, true)) {
162-
// Workaround for JRFCAF-1344: Underlying stream close() may be thread-unsafe
163-
// and as such the close() may result in an IOException at the socket input stream level,
164-
// if the close() gets called at once from multiple threads somehow.
156+
if (!closed) {
165157
try {
166158
in.close();
167159
} catch (IOException ex) {
168-
// This means that the underlying socket stream got closed by other thread somehow
169-
LOGGER.log(Level.FINE, LocalizationMessages.MESSAGE_CONTENT_INPUT_STREAM_CLOSE_FAILED(), ex);
160+
// This e.g. means that the underlying socket stream got closed by other thread somehow...
161+
throw new ProcessingException(LocalizationMessages.MESSAGE_CONTENT_INPUT_STREAM_CLOSE_FAILED(), ex);
162+
} finally {
163+
closed = true;
170164
}
171165
}
172166
}
@@ -230,7 +224,7 @@ public boolean isEmpty() {
230224
* @throws IllegalStateException in case the entity input stream has been closed.
231225
*/
232226
public void ensureNotClosed() throws IllegalStateException {
233-
if (closed.get()) {
227+
if (closed) {
234228
throw new IllegalStateException(LocalizationMessages.ERROR_ENTITY_STREAM_CLOSED());
235229
}
236230
}
@@ -241,7 +235,7 @@ public void ensureNotClosed() throws IllegalStateException {
241235
* @return {@code true} if the stream has been closed, {@code false} otherwise.
242236
*/
243237
public boolean isClosed() {
244-
return closed.get();
238+
return closed;
245239
}
246240

247241
/**

core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
* @author Marek Potociar (marek.potociar at oracle.com)
8686
*/
8787
public abstract class InboundMessageContext {
88+
8889
private static final InputStream EMPTY = new InputStream() {
8990

9091
@Override
@@ -121,6 +122,7 @@ public boolean markSupported() {
121122
* is used to control the execution of interceptors.
122123
*/
123124
private static class EntityContent extends EntityInputStream {
125+
124126
private boolean buffered;
125127

126128
EntityContent() {
@@ -266,7 +268,7 @@ private static List<String> iterableToList(final Iterable<?> values) {
266268

267269
/**
268270
* Get a message header as a single string value.
269-
*
271+
* <p/>
270272
* Each single header value is converted to String using a
271273
* {@link javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate} if one is available
272274
* via {@link javax.ws.rs.ext.RuntimeDelegate#createHeaderDelegate(java.lang.Class)}
@@ -275,10 +277,10 @@ private static List<String> iterableToList(final Iterable<?> values) {
275277
*
276278
* @param name the message header.
277279
* @return the message header value. If the message header is not present then
278-
* {@code null} is returned. If the message header is present but has no
279-
* value then the empty string is returned. If the message header is present
280-
* more than once then the values of joined together and separated by a ','
281-
* character.
280+
* {@code null} is returned. If the message header is present but has no
281+
* value then the empty string is returned. If the message header is present
282+
* more than once then the values of joined together and separated by a ','
283+
* character.
282284
*/
283285
public String getHeaderString(String name) {
284286
List<String> values = this.headers.get(name);
@@ -400,7 +402,7 @@ public Set<MatchingEntityTag> getIfNoneMatch() {
400402
/**
401403
* Get the language of the entity.
402404
*
403-
* @return the language of the entity or {@code null} if not specified
405+
* @return the language of the entity or {@code null} if not specified.
404406
*/
405407
public Locale getLanguage() {
406408
return singleHeader(HttpHeaders.CONTENT_LANGUAGE, new Function<String, Locale>() {
@@ -418,8 +420,7 @@ public Locale apply(String input) {
418420
/**
419421
* Get Content-Length value.
420422
*
421-
* @return Content-Length as integer if present and valid number. In other
422-
* cases returns -1.
423+
* @return Content-Length as integer if present and valid number. In other cases returns -1.
423424
*/
424425
public int getLength() {
425426
return singleHeader(HttpHeaders.CONTENT_LENGTH, new Function<String, Integer>() {
@@ -438,7 +439,7 @@ public Integer apply(String input) {
438439
* Get the media type of the entity.
439440
*
440441
* @return the media type or {@code null} if not specified (e.g. there's no
441-
* message entity).
442+
* message entity).
442443
*/
443444
public MediaType getMediaType() {
444445
return singleHeader(HttpHeaders.CONTENT_TYPE, new Function<String, MediaType>() {
@@ -457,7 +458,7 @@ public MediaType apply(String input) {
457458
* Get a list of media types that are acceptable for a request.
458459
*
459460
* @return a read-only list of requested response media types sorted according
460-
* to their q-value, with highest preference first.
461+
* to their q-value, with highest preference first.
461462
*/
462463
public List<AcceptableMediaType> getQualifiedAcceptableMediaTypes() {
463464
final String value = getHeaderString(HttpHeaders.ACCEPT);
@@ -477,7 +478,7 @@ public List<AcceptableMediaType> getQualifiedAcceptableMediaTypes() {
477478
* Get a list of languages that are acceptable for the message.
478479
*
479480
* @return a read-only list of acceptable languages sorted according
480-
* to their q-value, with highest preference first.
481+
* to their q-value, with highest preference first.
481482
*/
482483
public List<AcceptableLanguageTag> getQualifiedAcceptableLanguages() {
483484
final String value = getHeaderString(HttpHeaders.ACCEPT_LANGUAGE);
@@ -497,7 +498,7 @@ public List<AcceptableLanguageTag> getQualifiedAcceptableLanguages() {
497498
* Get the list of language tag from the "Accept-Charset" of an HTTP request.
498499
*
499500
* @return The list of AcceptableToken. This list
500-
* is ordered with the highest quality acceptable charset occurring first.
501+
* is ordered with the highest quality acceptable charset occurring first.
501502
*/
502503
public List<AcceptableToken> getQualifiedAcceptCharset() {
503504
final String acceptCharset = getHeaderString(HttpHeaders.ACCEPT_CHARSET);
@@ -515,7 +516,7 @@ public List<AcceptableToken> getQualifiedAcceptCharset() {
515516
* Get the list of language tag from the "Accept-Charset" of an HTTP request.
516517
*
517518
* @return The list of AcceptableToken. This list
518-
* is ordered with the highest quality acceptable charset occurring first.
519+
* is ordered with the highest quality acceptable charset occurring first.
519520
*/
520521
public List<AcceptableToken> getQualifiedAcceptEncoding() {
521522
final String acceptEncoding = getHeaderString(HttpHeaders.ACCEPT_ENCODING);
@@ -553,7 +554,7 @@ public Map<String, Cookie> getRequestCookies() {
553554
* Get the allowed HTTP methods from the Allow HTTP header.
554555
*
555556
* @return the allowed HTTP methods, all methods will returned as upper case
556-
* strings.
557+
* strings.
557558
*/
558559
public Set<String> getAllowedMethods() {
559560
final String allowed = getHeaderString(HttpHeaders.ALLOW);
@@ -642,7 +643,7 @@ public URI apply(String value) {
642643
* Get the links attached to the message as header.
643644
*
644645
* @return links, may return empty {@link java.util.Set} if no links are present. Never
645-
* returns {@code null}.
646+
* returns {@code null}.
646647
*/
647648
public Set<Link> getLinks() {
648649
List<String> links = this.headers.get(HttpHeaders.LINK);
@@ -666,7 +667,7 @@ public Set<Link> getLinks() {
666667
*
667668
* @param relation link relation.
668669
* @return {@code true} if the for the relation link exists, {@code false}
669-
* otherwise.
670+
* otherwise.
670671
*/
671672
public boolean hasLink(String relation) {
672673
for (Link link : getLinks()) {
@@ -701,7 +702,7 @@ public Link getLink(String relation) {
701702
*
702703
* @param relation link relation.
703704
* @return the link builder for the relation, otherwise {@code null} if not
704-
* present.
705+
* present.
705706
*/
706707
public Link.Builder getLinkBuilder(String relation) {
707708
Link link = getLink(relation);
@@ -735,12 +736,12 @@ public void setWorkers(MessageBodyWorkers workers) {
735736
/**
736737
* Check if there is a non-empty entity input stream is available in the
737738
* message.
738-
*
739+
* <p/>
739740
* The method returns {@code true} if the entity is present, returns
740741
* {@code false} otherwise.
741742
*
742743
* @return {@code true} if there is an entity present in the message,
743-
* {@code false} otherwise.
744+
* {@code false} otherwise.
744745
*/
745746
public boolean hasEntity() {
746747
entityContent.ensureNotClosed();
@@ -785,7 +786,6 @@ public <T> T readEntity(Class<T> rawType, PropertiesDelegate propertiesDelegate)
785786
return readEntity(rawType, rawType, EMPTY_ANNOTATIONS, propertiesDelegate);
786787
}
787788

788-
789789
/**
790790
* Read entity from a context entity input stream.
791791
*
@@ -831,14 +831,14 @@ public <T> T readEntity(Class<T> rawType, Type type, Annotation[] annotations, P
831831

832832
entityContent.ensureNotClosed();
833833

834-
// TODO: revise if we need to re-introduce the check for performance reasons or once non-blocking I/O is supported.
835-
// The code has been commended out because in case of streaming input (e.g. SSE) the call might block until a first
836-
// byte is available, which would make e.g. the SSE EventSource construction or EventSource.open() method to block
837-
// until a first event is received, which is undesirable.
838-
//
839-
// if (entityContent.isEmpty()) {
840-
// return null;
841-
// }
834+
// TODO: revise if we need to re-introduce the check for performance reasons or once non-blocking I/O is supported.
835+
// The code has been commended out because in case of streaming input (e.g. SSE) the call might block until a first
836+
// byte is available, which would make e.g. the SSE EventSource construction or EventSource.open() method to block
837+
// until a first event is received, which is undesirable.
838+
//
839+
// if (entityContent.isEmpty()) {
840+
// return null;
841+
// }
842842

843843
if (workers == null) {
844844
return null;
@@ -847,7 +847,6 @@ public <T> T readEntity(Class<T> rawType, Type type, Annotation[] annotations, P
847847
MediaType mediaType = getMediaType();
848848
mediaType = mediaType == null ? MediaType.APPLICATION_OCTET_STREAM_TYPE : mediaType;
849849

850-
851850
boolean shouldClose = !buffered;
852851
try {
853852
T t = (T) workers.readFrom(
@@ -868,7 +867,11 @@ public <T> T readEntity(Class<T> rawType, Type type, Annotation[] annotations, P
868867
throw new ProcessingException(LocalizationMessages.ERROR_READING_ENTITY_FROM_INPUT_STREAM(), ex);
869868
} finally {
870869
if (shouldClose) {
871-
entityContent.close();
870+
// Workaround for JRFCAF-1344: the underlying stream close() implementation may be thread-unsafe
871+
// and as such the close() may result in an IOException at the socket input stream level,
872+
// if the close() gets called at once from multiple threads somehow.
873+
// We want to ignore these exceptions in the readEntity/bufferEntity operations though.
874+
ReaderWriter.safelyClose(entityContent);
872875
}
873876
}
874877
}
@@ -877,8 +880,7 @@ public <T> T readEntity(Class<T> rawType, Type type, Annotation[] annotations, P
877880
* Buffer the entity stream (if not empty).
878881
*
879882
* @return {@code true} if the entity input stream was successfully buffered.
880-
* @throws javax.ws.rs.ProcessingException
881-
* in case of an IO error.
883+
* @throws javax.ws.rs.ProcessingException in case of an IO error.
882884
*/
883885
public boolean bufferEntity() throws ProcessingException {
884886
entityContent.ensureNotClosed();
@@ -893,7 +895,11 @@ public boolean bufferEntity() throws ProcessingException {
893895
try {
894896
ReaderWriter.writeTo(entityStream, baos);
895897
} finally {
896-
entityStream.close();
898+
// Workaround for JRFCAF-1344: the underlying stream close() implementation may be thread-unsafe
899+
// and as such the close() may result in an IOException at the socket input stream level,
900+
// if the close() gets called at once from multiple threads somehow.
901+
// We want to ignore these exceptions in the readEntity/bufferEntity operations though.
902+
ReaderWriter.safelyClose(entityStream);
897903
}
898904

899905
entityContent.setContent(new ByteArrayInputStream(baos.toByteArray()), true);

core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,8 +1085,10 @@ public Object readFrom(final Class<?> rawType,
10851085
final Object instance = executor.proceed();
10861086
if (!(instance instanceof Closeable) && !(instance instanceof Source)) {
10871087
final InputStream stream = executor.getInputStream();
1088-
if (stream != null) {
1089-
stream.close();
1088+
if (stream != entityStream && stream != null) {
1089+
// We only close stream if it differs from the received entity stream,
1090+
// otherwise we let the caller close the stream.
1091+
ReaderWriter.safelyClose(stream);
10901092
}
10911093
}
10921094

core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderInterceptorExecutor.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
* only if the new code is made subject to such option by the copyright
3838
* holder.
3939
*/
40-
4140
package org.glassfish.jersey.message.internal;
4241

4342
import java.io.IOException;
@@ -83,16 +82,16 @@ public final class ReaderInterceptorExecutor extends InterceptorExecutor<ReaderI
8382

8483
private static final Logger LOGGER = Logger.getLogger(ReaderInterceptorExecutor.class.getName());
8584

86-
private InputStream inputStream;
8785
private final MultivaluedMap<String, String> headers;
88-
8986
private final Iterator<ReaderInterceptor> interceptors;
90-
private int processedCount;
9187
private final MessageBodyWorkers workers;
9288
private final boolean translateNce;
9389

9490
private final ServiceLocator serviceLocator;
9591

92+
private InputStream inputStream;
93+
private int processedCount;
94+
9695
/**
9796
* Constructs a new executor to read given type from provided {@link InputStream entityStream}.
9897
*

0 commit comments

Comments
 (0)