This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new ecd9319a19 Add Optional-returning accessors to
SerializerSet/ParserSet; opt/opte cleanup; harden petstore Spring Boot test
ecd9319a19 is described below
commit ecd9319a1925c3d82c4e1eaaf0c5e221c857029e
Author: James Bognar <[email protected]>
AuthorDate: Mon Jun 29 08:00:17 2026 -0400
Add Optional-returning accessors to SerializerSet/ParserSet; opt/opte
cleanup; harden petstore Spring Boot test
- TODO-190: SerializerSet/ParserSet and RestClient expose Optional-based
getSerializerMatch/getParserMatch accessors.
- Convert remaining Optional.empty()/Optional.ofNullable(...) to
opte()/opt() helpers.
- Add cold-start warm-up + generous timeouts to PetstoreSpringboot_Test to
fix CI flake.
Co-authored-by: Cursor <[email protected]>
---
.../apache/juneau/marshall/parser/ParserSet.java | 38 ++++-----
.../juneau/marshall/serializer/SerializerSet.java | 60 +++++++-------
.../juneau/marshall/parser/ParserSet_Test.java | 48 +++++------
.../marshall/serializer/SerializerSet_Test.java | 82 +++++++++----------
.../juneau/marshall/transforms/ObjectSwapTest.java | 6 +-
.../juneau/rest/client/RemoteClient_Test.java | 5 +-
.../client/RemoteProxy_FeatureParity_Test.java | 69 ++++++++++++----
.../client/RemoteProxy_NextGenParityGaps_Test.java | 4 +-
.../rest/client/RestClientFeatures_Test.java | 3 +
.../mixin/MixinInheritance_NoInherit_Test.java | 8 +-
.../rest/mixin/MixinInheritance_Parsers_Test.java | 18 ++--
.../mixin/MixinInheritance_Serializers_Test.java | 10 +--
.../springboot/PetstoreSpringboot_Test.java | 52 +++++++++++-
.../juneau/rest/client/classic/RestClient.java | 4 +-
.../apache/juneau/rest/client/ResponseBody.java | 17 ++--
.../org/apache/juneau/rest/client/RestClient.java | 88 ++++++++++++--------
.../org/apache/juneau/rest/client/RestRequest.java | 10 ++-
.../juneau/rest/client/remote/RemoteClient.java | 34 ++++----
.../rest/client/ResponseBody_Cursor_Test.java | 4 +-
.../rest/client/RestClient_Negotiation_Test.java | 95 +++++++++++++++-------
.../apache/juneau/rest/mock/MockRestClient.java | 2 +
.../rest/mock/NextGenContentNegotiation_Test.java | 3 +-
.../mock/RemoteCursorBinding_NextGen_Test.java | 29 ++++---
.../apache/juneau/rest/server/RestResponse.java | 2 +-
.../rest/server/httppart/RequestContent.java | 2 +-
.../swagger/BasicSwaggerProviderSession.java | 2 +-
.../rest/server/vars/SerializedRequestAttrVar.java | 2 +-
.../rest/server/vars/RestServerVars_Test.java | 5 +-
28 files changed, 433 insertions(+), 269 deletions(-)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parser/ParserSet.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parser/ParserSet.java
index db1becf055..bac451b243 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parser/ParserSet.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parser/ParserSet.java
@@ -70,7 +70,7 @@ import org.apache.juneau.marshall.*;
* .build();
*
* <jc>// Find the appropriate parser by Content-Type</jc>
- * ReaderParser <jv>parser</jv> =
(ReaderParser)<jv>parsers</jv>.getParser(<js>"text/json"</js>);
+ * ReaderParser <jv>parser</jv> =
(ReaderParser)<jv>parsers</jv>.getParser(<js>"text/json"</js>).orElseThrow();
<jc>// Throws if no parser matched the Content-Type.</jc>
*
* <jc>// Parse a bean from JSON</jc>
* String <jv>json</jv> = <js>"{...}"</js>;
@@ -561,33 +561,31 @@ public class ParserSet {
/**
* Same as {@link #getParserMatch(MediaType)} but returns just the
matched parser.
*
- * @param mediaType The HTTP media type.
- * @return The parser that matched the media type, or <jk>null</jk> if
no match was made.
+ * @param mediaType The HTTP media type. Can be <jk>null</jk>.
+ * @return The parser that matched the media type, or {@link
Optional#empty()} if no match was made.
*/
- public Parser getParser(MediaType mediaType) {
- ParserMatch pm = getParserMatch(mediaType);
- return pm == null ? null : pm.getParser();
+ public Optional<Parser> getParser(MediaType mediaType) {
+ return getParserMatch(mediaType).map(ParserMatch::getParser);
}
/**
* Same as {@link #getParserMatch(String)} but returns just the matched
parser.
*
* @param contentTypeHeader The HTTP <l>Content-Type</l> header string.
- * @return The parser that matched the content type header, or
<jk>null</jk> if no match was made.
+ * @return The parser that matched the content type header, or {@link
Optional#empty()} if no match was made.
*/
- public Parser getParser(String contentTypeHeader) {
- ParserMatch pm = getParserMatch(contentTypeHeader);
- return pm == null ? null : pm.getParser();
+ public Optional<Parser> getParser(String contentTypeHeader) {
+ return
getParserMatch(contentTypeHeader).map(ParserMatch::getParser);
}
/**
* Same as {@link #getParserMatch(String)} but matches using a {@link
MediaType} instance.
*
- * @param mediaType The HTTP <l>Content-Type</l> header value as a
media type.
- * @return The parser and media type that matched the media type, or
<jk>null</jk> if no match was made.
+ * @param mediaType The HTTP <l>Content-Type</l> header value as a
media type. Can be <jk>null</jk>.
+ * @return The parser and media type that matched the media type, or
{@link Optional#empty()} if no match was made.
*/
- public ParserMatch getParserMatch(MediaType mediaType) {
- return getParserMatch(mediaType.toString());
+ public Optional<ParserMatch> getParserMatch(MediaType mediaType) {
+ return mediaType == null ? opte() :
getParserMatch(mediaType.toString());
}
/**
@@ -596,13 +594,15 @@ public class ParserSet {
* <p>
* The returned object includes both the parser and media type that
matched.
*
- * @param contentTypeHeader The HTTP <l>Content-Type</l> header value.
- * @return The parser and media type that matched the content type
header, or <jk>null</jk> if no match was made.
+ * @param contentTypeHeader The HTTP <l>Content-Type</l> header value.
Can be <jk>null</jk>.
+ * @return The parser and media type that matched the content type
header, or {@link Optional#empty()} if no match was made.
*/
- public ParserMatch getParserMatch(String contentTypeHeader) {
+ public Optional<ParserMatch> getParserMatch(String contentTypeHeader) {
+ if (contentTypeHeader == null)
+ return opte();
ParserMatch pm = cache.get(contentTypeHeader);
if (nn(pm))
- return pm;
+ return opt(pm);
var ct = MediaType.of(contentTypeHeader);
int match = ct.match(l(mediaTypes));
@@ -612,7 +612,7 @@ public class ParserSet {
cache.putIfAbsent(contentTypeHeader, pm);
}
- return cache.get(contentTypeHeader);
+ return opt(cache.get(contentTypeHeader));
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerSet.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerSet.java
index cb6c30bed8..ce53466f28 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerSet.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerSet.java
@@ -69,7 +69,8 @@ import org.apache.juneau.marshall.*;
*
* <jc>// Find the appropriate serializer by Accept type</jc>
* WriterSerializer <jv>serializer</jv> = <jv>serializers</jv>
- * .getWriterSerializer(<js>"text/foo, text/json;q=0.8,
text/*;q:0.6, *\/*;q=0.0"</js>);
+ * .getWriterSerializer(<js>"text/foo, text/json;q=0.8,
text/*;q:0.6, *\/*;q=0.0"</js>)
+ * .orElseThrow(); <jc>// Throws if no serializer matched the
Accept header.</jc>
*
* <jc>// Serialize a bean to JSON text </jc>
* AddressBook <jv>addressBook</jv> = <jk>new</jk> AddressBook(); <jc>//
Bean to serialize.</jc>
@@ -567,33 +568,30 @@ public class SerializerSet {
* Same as {@link #getSerializerMatch(MediaType)} but returns just the
matched serializer.
*
* @param mediaType The HTTP media type.
- * @return The serializer that matched the accept header, or
<jk>null</jk> if no match was made.
+ * @return The serializer that matched the accept header, or {@link
Optional#empty()} if no match was made.
*/
- public Serializer getSerializer(MediaType mediaType) {
- if (mediaType == null)
- return null;
- return getSerializer(mediaType.toString());
+ public Optional<Serializer> getSerializer(MediaType mediaType) {
+ return
getSerializerMatch(mediaType).map(SerializerMatch::getSerializer);
}
/**
* Same as {@link #getSerializerMatch(String)} but returns just the
matched serializer.
*
* @param acceptHeader The HTTP <l>Accept</l> header string.
- * @return The serializer that matched the accept header, or
<jk>null</jk> if no match was made.
+ * @return The serializer that matched the accept header, or {@link
Optional#empty()} if no match was made.
*/
- public Serializer getSerializer(String acceptHeader) {
- SerializerMatch sm = getSerializerMatch(acceptHeader);
- return sm == null ? null : sm.getSerializer();
+ public Optional<Serializer> getSerializer(String acceptHeader) {
+ return
getSerializerMatch(acceptHeader).map(SerializerMatch::getSerializer);
}
/**
* Same as {@link #getSerializerMatch(String)} but matches using a
{@link MediaType} instance.
*
- * @param mediaType The HTTP media type.
- * @return The serializer and media type that matched the media type,
or <jk>null</jk> if no match was made.
+ * @param mediaType The HTTP media type. Can be <jk>null</jk>.
+ * @return The serializer and media type that matched the media type,
or {@link Optional#empty()} if no match was made.
*/
- public SerializerMatch getSerializerMatch(MediaType mediaType) {
- return getSerializerMatch(mediaType.toString());
+ public Optional<SerializerMatch> getSerializerMatch(MediaType
mediaType) {
+ return mediaType == null ? opte() :
getSerializerMatch(mediaType.toString());
}
/**
@@ -617,14 +615,14 @@ public class SerializerSet {
* The returned object includes both the serializer and media type that
matched.
*
* @param acceptHeader The HTTP <l>Accept</l> header string.
- * @return The serializer and media type that matched the accept
header, or <jk>null</jk> if no match was made.
+ * @return The serializer and media type that matched the accept
header, or {@link Optional#empty()} if no match was made.
*/
- public SerializerMatch getSerializerMatch(String acceptHeader) {
+ public Optional<SerializerMatch> getSerializerMatch(String
acceptHeader) {
if (acceptHeader == null)
- return null;
+ return opte();
SerializerMatch sm = cache.get(acceptHeader);
if (nn(sm))
- return sm;
+ return opt(sm);
var a = MediaRanges.of(acceptHeader);
int match = a.match(mediaRangesList);
@@ -633,7 +631,7 @@ public class SerializerSet {
cache.putIfAbsent(acceptHeader, sm);
}
- return cache.get(acceptHeader);
+ return opt(cache.get(acceptHeader));
}
/**
@@ -647,20 +645,20 @@ public class SerializerSet {
* Same as {@link #getSerializer(MediaType)}, but casts it to a {@link
OutputStreamSerializer}.
*
* @param mediaType The HTTP media type.
- * @return The serializer that matched the accept header, or
<jk>null</jk> if no match was made.
+ * @return The serializer that matched the accept header, or {@link
Optional#empty()} if no match was made.
*/
- public OutputStreamSerializer getStreamSerializer(MediaType mediaType) {
- return (OutputStreamSerializer)getSerializer(mediaType);
+ public Optional<OutputStreamSerializer> getStreamSerializer(MediaType
mediaType) {
+ return
getSerializer(mediaType).map(OutputStreamSerializer.class::cast);
}
/**
* Same as {@link #getSerializer(String)}, but casts it to an {@link
OutputStreamSerializer}.
*
* @param acceptHeader The HTTP <l>Accept</l> header string.
- * @return The serializer that matched the accept header, or
<jk>null</jk> if no match was made.
+ * @return The serializer that matched the accept header, or {@link
Optional#empty()} if no match was made.
*/
- public OutputStreamSerializer getStreamSerializer(String acceptHeader) {
- return (OutputStreamSerializer)getSerializer(acceptHeader);
+ public Optional<OutputStreamSerializer> getStreamSerializer(String
acceptHeader) {
+ return
getSerializer(acceptHeader).map(OutputStreamSerializer.class::cast);
}
/**
@@ -677,20 +675,20 @@ public class SerializerSet {
* Same as {@link #getSerializer(MediaType)}, but casts it to a {@link
WriterSerializer}.
*
* @param mediaType The HTTP media type.
- * @return The serializer that matched the accept header, or
<jk>null</jk> if no match was made.
+ * @return The serializer that matched the accept header, or {@link
Optional#empty()} if no match was made.
*/
- public WriterSerializer getWriterSerializer(MediaType mediaType) {
- return (WriterSerializer)getSerializer(mediaType);
+ public Optional<WriterSerializer> getWriterSerializer(MediaType
mediaType) {
+ return
getSerializer(mediaType).map(WriterSerializer.class::cast);
}
/**
* Same as {@link #getSerializer(String)}, but casts it to a {@link
WriterSerializer}.
*
* @param acceptHeader The HTTP <l>Accept</l> header string.
- * @return The serializer that matched the accept header, or
<jk>null</jk> if no match was made.
+ * @return The serializer that matched the accept header, or {@link
Optional#empty()} if no match was made.
*/
- public WriterSerializer getWriterSerializer(String acceptHeader) {
- return (WriterSerializer)getSerializer(acceptHeader);
+ public Optional<WriterSerializer> getWriterSerializer(String
acceptHeader) {
+ return
getSerializer(acceptHeader).map(WriterSerializer.class::cast);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/parser/ParserSet_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/parser/ParserSet_Test.java
index ac7d23ac14..182ccafe32 100755
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/parser/ParserSet_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/parser/ParserSet_Test.java
@@ -38,20 +38,20 @@ class ParserSet_Test extends TestBase {
@Test void a01_parserGroupMatching() {
var s = ParserSet.create().add(Parser1.class, Parser2.class,
Parser3.class).build();
- assertInstanceOf(Parser1.class, s.getParser("text/foo"));
- assertInstanceOf(Parser1.class, s.getParser("text/foo_a"));
- assertInstanceOf(Parser1.class, s.getParser("text/foo_a+xxx"));
- assertInstanceOf(Parser1.class, s.getParser("text/xxx+foo_a"));
- assertInstanceOf(Parser2.class, s.getParser("text/foo+bar"));
- assertInstanceOf(Parser2.class, s.getParser("text/foo+bar_a"));
- assertInstanceOf(Parser2.class, s.getParser("text/bar+foo"));
- assertInstanceOf(Parser2.class,
s.getParser("text/bar+foo+xxx"));
- assertInstanceOf(Parser3.class, s.getParser("text/baz"));
- assertInstanceOf(Parser3.class, s.getParser("text/baz_a"));
- assertInstanceOf(Parser3.class, s.getParser("text/baz+yyy"));
- assertInstanceOf(Parser3.class, s.getParser("text/baz_a+yyy"));
- assertInstanceOf(Parser3.class, s.getParser("text/yyy+baz"));
- assertInstanceOf(Parser3.class, s.getParser("text/yyy+baz_a"));
+ assertInstanceOf(Parser1.class,
s.getParser("text/foo").orElseThrow());
+ assertInstanceOf(Parser1.class,
s.getParser("text/foo_a").orElseThrow());
+ assertInstanceOf(Parser1.class,
s.getParser("text/foo_a+xxx").orElseThrow());
+ assertInstanceOf(Parser1.class,
s.getParser("text/xxx+foo_a").orElseThrow());
+ assertInstanceOf(Parser2.class,
s.getParser("text/foo+bar").orElseThrow());
+ assertInstanceOf(Parser2.class,
s.getParser("text/foo+bar_a").orElseThrow());
+ assertInstanceOf(Parser2.class,
s.getParser("text/bar+foo").orElseThrow());
+ assertInstanceOf(Parser2.class,
s.getParser("text/bar+foo+xxx").orElseThrow());
+ assertInstanceOf(Parser3.class,
s.getParser("text/baz").orElseThrow());
+ assertInstanceOf(Parser3.class,
s.getParser("text/baz_a").orElseThrow());
+ assertInstanceOf(Parser3.class,
s.getParser("text/baz+yyy").orElseThrow());
+ assertInstanceOf(Parser3.class,
s.getParser("text/baz_a+yyy").orElseThrow());
+ assertInstanceOf(Parser3.class,
s.getParser("text/yyy+baz").orElseThrow());
+ assertInstanceOf(Parser3.class,
s.getParser("text/yyy+baz_a").orElseThrow());
}
public static class Parser1 extends JsonParser { public
Parser1(JsonParser.Builder<?> b) { super(b.consumes("text/foo,text/foo_a")); }}
@@ -94,7 +94,7 @@ class ParserSet_Test extends TestBase {
@Test void b01_builder_addInstancesDirectly() {
var instance = new P1(JsonParser.create().consumes("text/1"));
var s = ParserSet.create().add(instance).build();
- assertInstanceOf(P1.class, s.getParser("text/1"));
+ assertInstanceOf(P1.class, s.getParser("text/1").orElseThrow());
}
@Test void b02_builder_clear_removesAllEntries() {
@@ -113,9 +113,9 @@ class ParserSet_Test extends TestBase {
var sb = ParserSet.create().add(P1.class, P2.class);
sb.set(ParserSet.Inherit.class, P3.class);
var s = sb.build();
- assertInstanceOf(P1.class, s.getParser("text/1"));
- assertInstanceOf(P2.class, s.getParser("text/2"));
- assertInstanceOf(P3.class, s.getParser("text/3"));
+ assertInstanceOf(P1.class, s.getParser("text/1").orElseThrow());
+ assertInstanceOf(P2.class, s.getParser("text/2").orElseThrow());
+ assertInstanceOf(P3.class, s.getParser("text/3").orElseThrow());
}
@Test void b05_builder_setWithInvalidClassThrows() {
@@ -160,8 +160,8 @@ class ParserSet_Test extends TestBase {
var sb1 = ParserSet.create().add(P1.class);
var sb2 = sb1.copy();
sb2.add(P2.class);
- assertTrue(sb2.build().getParser("text/2") instanceof P2);
- assertNull(sb1.build().getParser("text/2"));
+ assertTrue(sb2.build().getParser("text/2").orElseThrow()
instanceof P2);
+ assertTrue(sb1.build().getParser("text/2").isEmpty());
}
@Test void b13_builder_beanContext_propagatesToBuilders() {
@@ -216,18 +216,18 @@ class ParserSet_Test extends TestBase {
@Test void b20_parserSet_copy_returnsNewBuilder() {
var s = ParserSet.create().add(P1.class).build();
var copy = s.copy();
- assertInstanceOf(P1.class, copy.build().getParser("text/1"));
+ assertInstanceOf(P1.class,
copy.build().getParser("text/1").orElseThrow());
}
@Test void b21_getParser_byMediaType() {
var s = ParserSet.create().add(P1.class).build();
- assertInstanceOf(P1.class, s.getParser(MediaType.of("text/1")));
- assertNull(s.getParser(MediaType.of("text/unknown")));
+ assertInstanceOf(P1.class,
s.getParser(MediaType.of("text/1")).orElseThrow());
+ assertTrue(s.getParser(MediaType.of("text/unknown")).isEmpty());
}
@Test void b22_add_parserWithNoArgConstructor_instantiatesDirectly() {
var s = ParserSet.create().add(SimpleParser.class).build();
- assertInstanceOf(SimpleParser.class,
s.getParser("text/simple"));
+ assertInstanceOf(SimpleParser.class,
s.getParser("text/simple").orElseThrow());
}
@Test void b23_copy_withParserInstance_coversNonBuilderBranch() {
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/serializer/SerializerSet_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/serializer/SerializerSet_Test.java
index 72fd45c182..3a3b8acb91 100755
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/serializer/SerializerSet_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/serializer/SerializerSet_Test.java
@@ -38,25 +38,25 @@ class SerializerSet_Test extends TestBase {
@Test void a01_serializerGroupMatching() {
var sg = SerializerSet.create().add(SA1.class, SA2.class,
SA3.class).build();
- assertInstanceOf(SA1.class, sg.getSerializer("text/foo"));
- assertInstanceOf(SA1.class, sg.getSerializer("text/foo_a"));
- assertInstanceOf(SA1.class, sg.getSerializer("text/xxx+foo_a"));
- assertInstanceOf(SA1.class, sg.getSerializer("text/foo_a+xxx"));
- assertInstanceOf(SA2.class, sg.getSerializer("text/foo+bar"));
- assertInstanceOf(SA2.class, sg.getSerializer("text/foo+bar_a"));
- assertInstanceOf(SA2.class, sg.getSerializer("text/bar+foo"));
- assertInstanceOf(SA2.class, sg.getSerializer("text/bar_a+foo"));
- assertInstanceOf(SA2.class,
sg.getSerializer("text/bar+foo+xxx"));
- assertInstanceOf(SA2.class,
sg.getSerializer("text/bar_a+foo+xxx"));
- assertInstanceOf(SA3.class, sg.getSerializer("text/baz"));
- assertInstanceOf(SA3.class, sg.getSerializer("text/baz_a"));
- assertInstanceOf(SA3.class, sg.getSerializer("text/baz+yyy"));
- assertInstanceOf(SA3.class, sg.getSerializer("text/baz_a+yyy"));
- assertInstanceOf(SA3.class, sg.getSerializer("text/yyy+baz"));
- assertInstanceOf(SA3.class, sg.getSerializer("text/yyy+baz_a"));
-
- assertInstanceOf(SA1.class,
sg.getSerializer("text/foo;q=0.9,text/foo+bar;q=0.8"));
- assertInstanceOf(SA2.class,
sg.getSerializer("text/foo;q=0.8,text/foo+bar;q=0.9"));
+ assertInstanceOf(SA1.class,
sg.getSerializer("text/foo").orElseThrow());
+ assertInstanceOf(SA1.class,
sg.getSerializer("text/foo_a").orElseThrow());
+ assertInstanceOf(SA1.class,
sg.getSerializer("text/xxx+foo_a").orElseThrow());
+ assertInstanceOf(SA1.class,
sg.getSerializer("text/foo_a+xxx").orElseThrow());
+ assertInstanceOf(SA2.class,
sg.getSerializer("text/foo+bar").orElseThrow());
+ assertInstanceOf(SA2.class,
sg.getSerializer("text/foo+bar_a").orElseThrow());
+ assertInstanceOf(SA2.class,
sg.getSerializer("text/bar+foo").orElseThrow());
+ assertInstanceOf(SA2.class,
sg.getSerializer("text/bar_a+foo").orElseThrow());
+ assertInstanceOf(SA2.class,
sg.getSerializer("text/bar+foo+xxx").orElseThrow());
+ assertInstanceOf(SA2.class,
sg.getSerializer("text/bar_a+foo+xxx").orElseThrow());
+ assertInstanceOf(SA3.class,
sg.getSerializer("text/baz").orElseThrow());
+ assertInstanceOf(SA3.class,
sg.getSerializer("text/baz_a").orElseThrow());
+ assertInstanceOf(SA3.class,
sg.getSerializer("text/baz+yyy").orElseThrow());
+ assertInstanceOf(SA3.class,
sg.getSerializer("text/baz_a+yyy").orElseThrow());
+ assertInstanceOf(SA3.class,
sg.getSerializer("text/yyy+baz").orElseThrow());
+ assertInstanceOf(SA3.class,
sg.getSerializer("text/yyy+baz_a").orElseThrow());
+
+ assertInstanceOf(SA1.class,
sg.getSerializer("text/foo;q=0.9,text/foo+bar;q=0.8").orElseThrow());
+ assertInstanceOf(SA2.class,
sg.getSerializer("text/foo;q=0.8,text/foo+bar;q=0.9").orElseThrow());
}
public static class SA1 extends JsonSerializer {
@@ -130,9 +130,9 @@ class SerializerSet_Test extends TestBase {
@Test void a03_mediaTypesWithMetaCharacters() {
var gb = SerializerSet.create().add(SC1.class, SC2.class,
SC3.class);
var g = gb.build();
- assertInstanceOf(SC1.class, g.getSerializer("text/foo"));
- assertInstanceOf(SC2.class, g.getSerializer("foo/json"));
- assertInstanceOf(SC3.class, g.getSerializer("foo/foo"));
+ assertInstanceOf(SC1.class,
g.getSerializer("text/foo").orElseThrow());
+ assertInstanceOf(SC2.class,
g.getSerializer("foo/json").orElseThrow());
+ assertInstanceOf(SC3.class,
g.getSerializer("foo/foo").orElseThrow());
}
public static class SC1 extends JsonSerializer {
@@ -166,7 +166,7 @@ class SerializerSet_Test extends TestBase {
@Test void b01_builder_addInstancesDirectly() {
var instance = new
SB1(JsonSerializer.create().accept("text/1"));
var s = SerializerSet.create().add(instance).build();
- assertInstanceOf(SB1.class, s.getSerializer("text/1"));
+ assertInstanceOf(SB1.class,
s.getSerializer("text/1").orElseThrow());
}
@Test void b02_builder_addInvalidClassThrows() {
@@ -178,9 +178,9 @@ class SerializerSet_Test extends TestBase {
var sb = SerializerSet.create().add(SB1.class, SB2.class);
sb.set(SerializerSet.Inherit.class, SB3.class);
var s = sb.build();
- assertInstanceOf(SB1.class, s.getSerializer("text/1"));
- assertInstanceOf(SB2.class, s.getSerializer("text/2"));
- assertInstanceOf(SB3.class, s.getSerializer("text/3"));
+ assertInstanceOf(SB1.class,
s.getSerializer("text/1").orElseThrow());
+ assertInstanceOf(SB2.class,
s.getSerializer("text/2").orElseThrow());
+ assertInstanceOf(SB3.class,
s.getSerializer("text/3").orElseThrow());
}
@Test void b04_builder_setWithInvalidClassThrows() {
@@ -225,8 +225,8 @@ class SerializerSet_Test extends TestBase {
var sb1 = SerializerSet.create().add(SB1.class);
var sb2 = sb1.copy();
sb2.add(SB2.class);
- assertNotNull(sb2.build().getSerializer("text/2"));
- assertNull(sb1.build().getSerializer("text/2"));
+ assertTrue(sb2.build().getSerializer("text/2").isPresent());
+ assertTrue(sb1.build().getSerializer("text/2").isEmpty());
}
@Test void b12_builder_beanContext_propagatesToBuilders() {
@@ -280,18 +280,18 @@ class SerializerSet_Test extends TestBase {
@Test void b19_serializerSet_copy_returnsNewBuilder() {
var s = SerializerSet.create().add(SB1.class).build();
var copy = s.copy();
- assertInstanceOf(SB1.class,
copy.build().getSerializer("text/1"));
+ assertInstanceOf(SB1.class,
copy.build().getSerializer("text/1").orElseThrow());
}
@Test void b20_getSerializer_byMediaType() {
var s = SerializerSet.create().add(SB1.class).build();
- assertInstanceOf(SB1.class,
s.getSerializer(MediaType.of("text/1")));
- assertNull(s.getSerializer(MediaType.of("text/unknown")));
+ assertInstanceOf(SB1.class,
s.getSerializer(MediaType.of("text/1")).orElseThrow());
+
assertTrue(s.getSerializer(MediaType.of("text/unknown")).isEmpty());
}
- @Test void b21_getSerializer_nullMediaType_returnsNull() {
+ @Test void b21_getSerializer_nullMediaType_returnsEmpty() {
var s = SerializerSet.create().add(SB1.class).build();
- assertNull(s.getSerializer((MediaType) null));
+ assertTrue(s.getSerializer((MediaType) null).isEmpty());
}
@Test void b22_builder_clear_removesAllEntries() {
@@ -303,22 +303,22 @@ class SerializerSet_Test extends TestBase {
@Test void b23_getSerializerMatch_byMediaType() {
var s = SerializerSet.create().add(SB1.class).build();
- assertNotNull(s.getSerializerMatch(MediaType.of("text/1")));
+
assertTrue(s.getSerializerMatch(MediaType.of("text/1")).isPresent());
}
- @Test void b24_getSerializerMatch_nullString_returnsNull() {
+ @Test void b24_getSerializerMatch_nullString_returnsEmpty() {
var s = SerializerSet.create().add(SB1.class).build();
- assertNull(s.getSerializerMatch((String) null));
+ assertTrue(s.getSerializerMatch((String) null).isEmpty());
}
@Test void b25_getWriterSerializer_byMediaType() {
var s = SerializerSet.create().add(SB1.class).build();
- assertInstanceOf(SB1.class,
s.getWriterSerializer(MediaType.of("text/1")));
+ assertInstanceOf(SB1.class,
s.getWriterSerializer(MediaType.of("text/1")).orElseThrow());
}
@Test void
b26_add_serializerWithNoArgConstructor_instantiatesDirectly() {
var s =
SerializerSet.create().add(SimpleSerializer.class).build();
- assertInstanceOf(SimpleSerializer.class,
s.getSerializer("text/simple"));
+ assertInstanceOf(SimpleSerializer.class,
s.getSerializer("text/simple").orElseThrow());
}
@Test void b27_copy_withSerializerInstance_coversNonBuilderBranch() {
@@ -328,9 +328,9 @@ class SerializerSet_Test extends TestBase {
assertFalse(copy.inner().isEmpty());
}
- @Test void b28_getStreamSerializer_withNoMatch_returnsNull() {
+ @Test void b28_getStreamSerializer_withNoMatch_returnsEmpty() {
var s = SerializerSet.create().add(SB1.class).build();
- assertNull(s.getStreamSerializer(MediaType.of("text/unknown")));
- assertNull(s.getStreamSerializer("text/unknown"));
+
assertTrue(s.getStreamSerializer(MediaType.of("text/unknown")).isEmpty());
+ assertTrue(s.getStreamSerializer("text/unknown").isEmpty());
}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/transforms/ObjectSwapTest.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/transforms/ObjectSwapTest.java
index 3a33cc75a2..c727179876 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/transforms/ObjectSwapTest.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/transforms/ObjectSwapTest.java
@@ -87,13 +87,13 @@ class ObjectSwapTest extends TestBase {
var myPojo = new MyPojo();
- var json = s.getWriterSerializer("text/json").serialize(myPojo);
+ var json =
s.getWriterSerializer("text/json").orElseThrow().serialize(myPojo);
assertEquals("'It\\'s JSON!'", json);
- var xml = s.getWriterSerializer("text/xml").serialize(myPojo);
+ var xml =
s.getWriterSerializer("text/xml").orElseThrow().serialize(myPojo);
assertEquals("<string>It's XML!</string>", xml);
- var html = s.getWriterSerializer("text/html").serialize(myPojo);
+ var html =
s.getWriterSerializer("text/html").orElseThrow().serialize(myPojo);
assertEquals("<string>It's something else!</string>", html);
}
}
\ No newline at end of file
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
index e174186e78..54e7cee561 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
@@ -24,6 +24,7 @@ import java.util.*;
import org.apache.juneau.http.*;
import org.apache.juneau.http.entity.*;
import org.apache.juneau.http.remote.*;
+import org.apache.juneau.marshall.json.*;
import org.apache.juneau.rest.client.remote.*;
import org.apache.juneau.rest.mock.*;
import org.junit.jupiter.api.*;
@@ -695,7 +696,9 @@ class RemoteClient_Test {
}
@Test void t02_object_return_falls_through_to_string() throws Exception
{
- try (var client =
RestClient.builder().transport(MockHttpTransport.of(200,
"hello")).rootUrl("http://x.com").build()) {
+ // Object-return fallthrough: the negotiated parser fails to
parse the plain body, so the raw string is returned.
+ // Requires a parser to be resolvable — configure JSON as the
default (the next-gen client has no implicit one).
+ try (var client =
RestClient.builder().transport(MockHttpTransport.of(200,
"hello")).rootUrl("http://x.com").defaultParser(JsonParser.DEFAULT).build()) {
var svc =
client.remote(T01_VoidBoxedReturnService.class);
assertEquals("hello", svc.getObject());
}
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_FeatureParity_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_FeatureParity_Test.java
index 2cbcb1a55d..f1c0330ff9 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_FeatureParity_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_FeatureParity_Test.java
@@ -28,6 +28,7 @@ import org.apache.juneau.http.header.*;
import org.apache.juneau.http.part.*;
import org.apache.juneau.http.remote.*;
import org.apache.juneau.http.response.*;
+import org.apache.juneau.marshall.json.*;
import org.apache.juneau.rest.mock.*;
import org.apache.juneau.rest.server.*;
import org.junit.jupiter.api.*;
@@ -238,6 +239,23 @@ class RemoteProxy_FeatureParity_Test {
@RestGet("/rest/remainder/*") public String
remainder(@PathRemainder String r) { return "r=" + r; }
}
+ /**
+ * JSON-negotiating fixture for the response-deserialization rows
(F15–F19, G11). Unlike
+ * {@link A_ParityResource} (which returns pre-serialized raw strings
and runs serializer-less), this resource
+ * configures a JSON serializer/parser and returns <i>typed</i>
objects, so the next-gen client — which now
+ * advertises {@code Accept: application/json} once a parser is
configured and has no implicit JSON fallback —
+ * negotiates a real {@code application/json} round-trip rather than
relying on the removed implicit default.
+ */
+ @Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+ public static class A_ParityJsonResource {
+ @RestGet("/rest/bean") public A_Bean bean() { var b = new
A_Bean(); b.setName("na44"); return b; }
+ @RestGet("/rest/list") public List<A_Bean> list() { var b =
new A_Bean(); b.setName("na44"); return List.of(b); }
+ @RestGet("/rest/map") public Map<String,String> map() {
return Map.of("k", "v"); }
+ @RestPatch("/rest/count") public Integer count(@Content String
body) { return 5; }
+ @RestGet("/rest/object") public Object object() { return 123; }
+ @RestPost("/rest/beanContent") public String
beanContent(@Content A_Bean bean) { return "posted:" + bean.getName(); }
+ }
+
//
=================================================================================================================
// Shared helpers
//
=================================================================================================================
@@ -308,6 +326,20 @@ class RemoteProxy_FeatureParity_Test {
return mrc.getClient().remote(A_ParityClient.class);
}
+ @SuppressWarnings({
+ "resource" // The negotiating client shares the
MockRestClient's (root-mounted) transport, closed by mrc; not closed separately.
+ })
+ private A_ParityClient parsingProxy(MockRestClient mrc) {
+ // Response-body deserialization needs a resolvable
parser; the next-gen client has no implicit JSON default,
+ // so configure JSON serializer + default parser
explicitly over the mock's (A_ParityJsonResource) transport.
+ return RestClient.builder()
+ .transport(mrc.getClient().getTransport())
+ .defaultSerializer(JsonSerializer.DEFAULT)
+ .defaultParser(JsonParser.DEFAULT)
+ .build()
+ .remote(A_ParityClient.class);
+ }
+
// ---- Active cells
-------------------------------------------------------------------------------------------
@Test void b01_get_F2_F20() throws Exception {
@@ -414,35 +446,35 @@ class RemoteProxy_FeatureParity_Test {
}
@Test void b23_returnBean_parsed_F15() throws Exception {
- try (var mrc =
MockRestClient.create(A_ParityResource.class)) {
- var bean = proxy(mrc).getBean();
+ try (var mrc =
MockRestClient.create(A_ParityJsonResource.class)) {
+ var bean = parsingProxy(mrc).getBean();
assertEquals("na44", bean.getName());
}
}
@Test void b24_returnList_parsed_F16() throws Exception {
- try (var mrc =
MockRestClient.create(A_ParityResource.class)) {
- var l = proxy(mrc).getList();
+ try (var mrc =
MockRestClient.create(A_ParityJsonResource.class)) {
+ var l = parsingProxy(mrc).getList();
assertEquals(1, l.size());
assertEquals("na44", l.get(0).getName());
}
}
@Test void b25_returnMap_parsed_F17() throws Exception {
- try (var mrc =
MockRestClient.create(A_ParityResource.class)) {
- assertEquals("v", proxy(mrc).getMap().get("k"));
+ try (var mrc =
MockRestClient.create(A_ParityJsonResource.class)) {
+ assertEquals("v",
parsingProxy(mrc).getMap().get("k"));
}
}
@Test void b26_returnInteger_parsed_F18() throws Exception {
- try (var mrc =
MockRestClient.create(A_ParityResource.class)) {
- assertEquals(Integer.valueOf(5),
proxy(mrc).patchCount("x"));
+ try (var mrc =
MockRestClient.create(A_ParityJsonResource.class)) {
+ assertEquals(Integer.valueOf(5),
parsingProxy(mrc).patchCount("x"));
}
}
@Test void b27_returnObject_parsed_F19() throws Exception {
- try (var mrc =
MockRestClient.create(A_ParityResource.class)) {
- assertEquals(123, proxy(mrc).getObject());
+ try (var mrc =
MockRestClient.create(A_ParityJsonResource.class)) {
+ assertEquals(123,
parsingProxy(mrc).getObject());
}
}
@@ -472,8 +504,8 @@ class RemoteProxy_FeatureParity_Test {
}
@Test void b32_optionalReturn_G11() throws Exception {
- try (var mrc =
MockRestClient.create(A_ParityResource.class)) {
-
assertTrue(proxy(mrc).getBeanOptional().isPresent());
+ try (var mrc =
MockRestClient.create(A_ParityJsonResource.class)) {
+
assertTrue(parsingProxy(mrc).getBeanOptional().isPresent());
}
}
@@ -570,7 +602,7 @@ class RemoteProxy_FeatureParity_Test {
@Nested class C_RealTransportColumn {
private RestClient client(List<TransportRequest> captured) {
- return
RestClient.builder().transport(captureTransport(captured).build()).rootUrl("http://x.com").build();
+ return
RestClient.builder().transport(captureTransport(captured).build()).rootUrl("http://x.com").defaultSerializer(JsonSerializer.DEFAULT).build();
}
// ---- Active cells
-------------------------------------------------------------------------------------------
@@ -797,7 +829,8 @@ class RemoteProxy_FeatureParity_Test {
var t = MockHttpTransport.builder()
.fallback(req ->
TransportResponse.builder().statusCode(200).body(new
ByteArrayInputStream("[{\"name\":\"na44\"}]".getBytes())).build())
.build();
- try (var c =
RestClient.builder().transport(t).rootUrl("http://x.com").build()) {
+ // Response carries no Content-Type, so resolve via the
explicit default parser (no implicit JSON fallback).
+ try (var c =
RestClient.builder().transport(t).rootUrl("http://x.com").defaultParser(JsonParser.DEFAULT).build())
{
var f =
c.remote(A_ParityClient.class).getListAsync();
var list = f.get();
assertNotNull(list);
@@ -836,9 +869,13 @@ class RemoteProxy_FeatureParity_Test {
}
}
+ @SuppressWarnings({
+ "resource" // The negotiating client shares the
MockRestClient's (root-mounted) transport, closed by mrc.
+ })
@Test void c43_jsonRoundTrip_F23() throws Exception {
- try (var mrc =
MockRestClient.create(A_ParityResource.class)) {
- var svc =
mrc.getClient().remote(A_ParityClient.class);
+ try (var mrc =
MockRestClient.create(A_ParityJsonResource.class);
+ var c =
RestClient.builder().transport(mrc.getClient().getTransport()).defaultSerializer(JsonSerializer.DEFAULT).defaultParser(JsonParser.DEFAULT).build())
{
+ var svc = c.remote(A_ParityClient.class);
var b = new A_Bean(); b.setName("na44");
assertTrue(svc.postBean(b).contains("na44"));
assertEquals("na44", svc.getBean().getName());
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_NextGenParityGaps_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_NextGenParityGaps_Test.java
index c4eb9a30d0..aabba808d7 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_NextGenParityGaps_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RemoteProxy_NextGenParityGaps_Test.java
@@ -199,7 +199,7 @@ class RemoteProxy_NextGenParityGaps_Test {
}
private static RestClient captureClient(List<TransportRequest>
captured) {
- return
RestClient.builder().transport(captureTransport(captured).build()).rootUrl("http://x.com").build();
+ return
RestClient.builder().transport(captureTransport(captured).build()).rootUrl("http://x.com").defaultSerializer(JsonSerializer.DEFAULT).build();
}
private static String readBody(TransportRequest req) throws IOException
{
@@ -1367,7 +1367,7 @@ class RemoteProxy_NextGenParityGaps_Test {
}
private static RestClient captureClient(List<TransportRequest>
captured, SerializerSet serializers, ParserSet parsers) {
- var b =
RestClient.builder().transport(captureTransport(captured).build()).rootUrl("http://x.com");
+ var b =
RestClient.builder().transport(captureTransport(captured).build()).rootUrl("http://x.com").defaultSerializer(JsonSerializer.DEFAULT);
if (serializers != null)
b.serializers(serializers);
if (parsers != null)
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RestClientFeatures_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RestClientFeatures_Test.java
index 9cfdc32fc7..8c25fabf99 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RestClientFeatures_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/client/RestClientFeatures_Test.java
@@ -26,6 +26,7 @@ import java.util.concurrent.atomic.*;
import org.apache.juneau.http.*;
import org.apache.juneau.http.entity.*;
+import org.apache.juneau.marshall.json.*;
import org.apache.juneau.rest.mock.*;
import org.junit.jupiter.api.*;
@@ -768,6 +769,7 @@ class RestClientFeatures_Test {
try (var client = RestClient.builder()
.transport(transport)
.rootUrl("http://x.com")
+ .defaultSerializer(JsonSerializer.DEFAULT)
.bodyConverters() // empty converter list
.build()) {
try (var r =
client.post("/").body("no-converter").run()) {
@@ -815,6 +817,7 @@ class RestClientFeatures_Test {
try (var client = RestClient.builder()
.transport(transport)
.rootUrl("http://x.com")
+ .defaultSerializer(JsonSerializer.DEFAULT)
.bodyConverters() // no converters
.build()) {
try (var r = client.post("/").body(new
ByteArrayInputStream(new byte[0])).run()) {
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_NoInherit_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_NoInherit_Test.java
index f16053abc5..9b7ec41929 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_NoInherit_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_NoInherit_Test.java
@@ -72,9 +72,9 @@ class MixinInheritance_NoInherit_Test extends TestBase {
var mixinCtx =
hostCtx.getMixinContexts().get(M_MixinS1Only.class);
assertNotNull(mixinCtx);
-
assertNotNull(mixinCtx.getSerializers().getSerializer("text/mixin-s1"),
+
assertTrue(mixinCtx.getSerializers().getSerializer("text/mixin-s1").isPresent(),
"Mixin's own MixinS1 must be present");
-
assertNull(mixinCtx.getSerializers().getSerializer("text/host-s1"),
+
assertTrue(mixinCtx.getSerializers().getSerializer("text/host-s1").isEmpty(),
"Mixin with noInherit=\"serializers\" must NOT see the
host's HostS1 (parent walk blocked)");
}
@@ -82,9 +82,9 @@ class MixinInheritance_NoInherit_Test extends TestBase {
MockRestClient.buildLax(Host.class);
var hostCtx = RestContext.getGlobalRegistry().get(Host.class);
-
assertNotNull(hostCtx.getSerializers().getSerializer("text/host-s1"),
+
assertTrue(hostCtx.getSerializers().getSerializer("text/host-s1").isPresent(),
"Host must retain its declared HostS1 serializer
regardless of mixin's noInherit");
-
assertNull(hostCtx.getSerializers().getSerializer("text/mixin-s1"),
+
assertTrue(hostCtx.getSerializers().getSerializer("text/mixin-s1").isEmpty(),
"Host must NOT pick up MixinS1 from a
noInherit-isolated mixin");
}
}
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Parsers_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Parsers_Test.java
index df9c19a717..3821b89809 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Parsers_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Parsers_Test.java
@@ -83,9 +83,9 @@ class MixinInheritance_Parsers_Test extends TestBase {
var mixinCtx = hostCtx.getMixinContexts().get(M_Empty.class);
assertNotNull(mixinCtx);
-
assertNotNull(hostCtx.getParsers().getParser(MediaType.of("text/host-p1")),
+
assertTrue(hostCtx.getParsers().getParser(MediaType.of("text/host-p1")).isPresent(),
"Host must register its declared HostP1 parser");
-
assertNotNull(mixinCtx.getParsers().getParser(MediaType.of("text/host-p1")),
+
assertTrue(mixinCtx.getParsers().getParser(MediaType.of("text/host-p1")).isPresent(),
"Mixin with no parser overrides must inherit the host's
HostP1 parser");
}
@@ -95,11 +95,11 @@ class MixinInheritance_Parsers_Test extends TestBase {
var mixinCtx =
hostCtx.getMixinContexts().get(M_AppendsMixinP1.class);
assertNotNull(mixinCtx);
-
assertNull(hostCtx.getParsers().getParser(MediaType.of("text/mixin-p1")),
+
assertTrue(hostCtx.getParsers().getParser(MediaType.of("text/mixin-p1")).isEmpty(),
"Host endpoint must NOT have MixinP1 — mixin
contributions are scoped to the mixin context");
-
assertNotNull(mixinCtx.getParsers().getParser(MediaType.of("text/mixin-p1")),
+
assertTrue(mixinCtx.getParsers().getParser(MediaType.of("text/mixin-p1")).isPresent(),
"Mixin endpoint must have MixinP1 via the mixin's own
@Rest(parsers=)");
-
assertNotNull(mixinCtx.getParsers().getParser(MediaType.of("text/host-p1")),
+
assertTrue(mixinCtx.getParsers().getParser(MediaType.of("text/host-p1")).isPresent(),
"Mixin endpoint must still have the host's HostP1
(inheritance walk)");
}
@@ -109,14 +109,14 @@ class MixinInheritance_Parsers_Test extends TestBase {
var mixinCtx =
hostCtx.getMixinContexts().get(M_NoInheritP1.class);
assertNotNull(mixinCtx);
-
assertNotNull(mixinCtx.getParsers().getParser(MediaType.of("text/mixin-p1")),
+
assertTrue(mixinCtx.getParsers().getParser(MediaType.of("text/mixin-p1")).isPresent(),
"Mixin's own MixinP1 must be present");
-
assertNull(mixinCtx.getParsers().getParser(MediaType.of("text/host-p1")),
+
assertTrue(mixinCtx.getParsers().getParser(MediaType.of("text/host-p1")).isEmpty(),
"Mixin with noInherit=\"parsers\" must NOT see the
host's HostP1 (parent walk blocked)");
-
assertNotNull(hostCtx.getParsers().getParser(MediaType.of("text/host-p1")),
+
assertTrue(hostCtx.getParsers().getParser(MediaType.of("text/host-p1")).isPresent(),
"Host must retain its HostP1 regardless of mixin's
noInherit");
-
assertNull(hostCtx.getParsers().getParser(MediaType.of("text/mixin-p1")),
+
assertTrue(hostCtx.getParsers().getParser(MediaType.of("text/mixin-p1")).isEmpty(),
"Host must NOT pick up MixinP1 from a
noInherit-isolated mixin");
}
}
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Serializers_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Serializers_Test.java
index dda7df7ba8..d28401a478 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Serializers_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_Serializers_Test.java
@@ -80,9 +80,9 @@ class MixinInheritance_Serializers_Test extends TestBase {
var mixinCtx = hostCtx.getMixinContexts().get(M_Empty.class);
assertNotNull(mixinCtx);
-
assertNotNull(hostCtx.getSerializers().getSerializer("text/host-s1"),
+
assertTrue(hostCtx.getSerializers().getSerializer("text/host-s1").isPresent(),
"Host must register its declared HostS1 serializer");
-
assertNotNull(mixinCtx.getSerializers().getSerializer("text/host-s1"),
+
assertTrue(mixinCtx.getSerializers().getSerializer("text/host-s1").isPresent(),
"Mixin with no serializer overrides must inherit the
host's HostS1 serializer");
}
@@ -92,11 +92,11 @@ class MixinInheritance_Serializers_Test extends TestBase {
var mixinCtx =
hostCtx.getMixinContexts().get(M_AppendsMixinS1.class);
assertNotNull(mixinCtx);
-
assertNull(hostCtx.getSerializers().getSerializer("text/mixin-s1"),
+
assertTrue(hostCtx.getSerializers().getSerializer("text/mixin-s1").isEmpty(),
"Host endpoint must NOT have MixinS1 — mixin
contributions are scoped to the mixin context");
-
assertNotNull(mixinCtx.getSerializers().getSerializer("text/mixin-s1"),
+
assertTrue(mixinCtx.getSerializers().getSerializer("text/mixin-s1").isPresent(),
"Mixin endpoint must have MixinS1 via the mixin's own
@Rest(serializers=)");
-
assertNotNull(mixinCtx.getSerializers().getSerializer("text/host-s1"),
+
assertTrue(mixinCtx.getSerializers().getSerializer("text/host-s1").isPresent(),
"Mixin endpoint must still have the host's HostS1
(inheritance walk)");
}
}
diff --git
a/juneau-petstore/juneau-petstore-springboot/src/test/java/org/apache/juneau/petstore/springboot/PetstoreSpringboot_Test.java
b/juneau-petstore/juneau-petstore-springboot/src/test/java/org/apache/juneau/petstore/springboot/PetstoreSpringboot_Test.java
index c7e4030858..9f1af7f582 100644
---
a/juneau-petstore/juneau-petstore-springboot/src/test/java/org/apache/juneau/petstore/springboot/PetstoreSpringboot_Test.java
+++
b/juneau-petstore/juneau-petstore-springboot/src/test/java/org/apache/juneau/petstore/springboot/PetstoreSpringboot_Test.java
@@ -43,6 +43,10 @@ import org.springframework.boot.test.web.server.*;
*/
@org.apache.juneau.testing.annotations.SpringbootTest
@SpringBootTest(classes = App.class, webEnvironment =
WebEnvironment.RANDOM_PORT)
+@SuppressWarnings({
+ "java:S8692", // warmUpServer() polls a real HTTP server against a
genuine wall-clock deadline; a fixed clock would break the retry loop.
+ "java:S2925" // warmUpServer() readiness loop needs a back-off between
retries; without it a ConnectException would busy-spin. No event/latch to await
and Awaitility isn't on the test classpath.
+})
class PetstoreSpringboot_Test {
@LocalServerPort
@@ -53,10 +57,50 @@ class PetstoreSpringboot_Test {
.followRedirects(HttpClient.Redirect.NEVER)
.build();
+ private static volatile boolean warmedUp;
+
+ /**
+ * Primes the root endpoint before the timed test methods run.
+ *
+ * <p>
+ * Spring Boot's {@code RANDOM_PORT} environment only waits for the
Tomcat connector to bind — not for the Juneau
+ * REST servlet to initialize. The first request to the group resource
({@code /}) forces one-time
+ * {@code RestContext} setup of the root <i>and all of its child
resources</i> (serializer/parser metadata,
+ * {@code HtmlDocSerializer} construction) in a single request. Under a
loaded CI agent this cold start can exceed
+ * the per-request timeout the test methods use (it timed out at
exactly 10s in build #2472). Absorbing that
+ * startup cost here once (with a generous budget + retry) removes the
race from {@code a01} while keeping the
+ * per-test timeouts tight. Mirrors the same guard in {@code
PetstoreJetty_Test}.
+ */
+ @BeforeEach
+ void warmUpServer() throws Exception {
+ if (warmedUp)
+ return;
+ var deadline = Instant.now().plusSeconds(30);
+ Exception last = null;
+ while (Instant.now().isBefore(deadline)) {
+ try {
+ var req = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:" +
port + "/"))
+ .timeout(Duration.ofSeconds(20))
+ .header("Accept", "text/html")
+ .GET()
+ .build();
+ if (HTTP.send(req,
BodyHandlers.ofString()).statusCode() == 200) {
+ warmedUp = true;
+ return;
+ }
+ } catch (HttpTimeoutException | ConnectException e) {
+ last = e;
+ }
+ Thread.sleep(250);
+ }
+ throw new IllegalStateException("Petstore Spring Boot server
did not become ready within 30s", last);
+ }
+
private HttpResponse<String> get(String path, String accept) throws
Exception {
var req = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:" + port + path))
- .timeout(Duration.ofSeconds(10))
+ .timeout(Duration.ofSeconds(30))
.header("Accept", accept)
.GET()
.build();
@@ -66,7 +110,7 @@ class PetstoreSpringboot_Test {
private HttpResponse<String> getWithAuth(String path, String accept,
String authValue) throws Exception {
var req = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:" + port + path))
- .timeout(Duration.ofSeconds(10))
+ .timeout(Duration.ofSeconds(30))
.header("Accept", accept)
.header("Authorization", authValue)
.GET()
@@ -77,7 +121,7 @@ class PetstoreSpringboot_Test {
private HttpResponse<String> post(String path, String contentType,
String body) throws Exception {
var req = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:" + port + path))
- .timeout(Duration.ofSeconds(10))
+ .timeout(Duration.ofSeconds(30))
.header("Content-Type", contentType)
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
@@ -88,7 +132,7 @@ class PetstoreSpringboot_Test {
private HttpResponse<String> delete(String path) throws Exception {
var req = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:" + port + path))
- .timeout(Duration.ofSeconds(10))
+ .timeout(Duration.ofSeconds(30))
.DELETE()
.build();
return HTTP.send(req, BodyHandlers.ofString());
diff --git
a/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/RestClient.java
b/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/RestClient.java
index fb04214008..6e1b7096f2 100644
---
a/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/RestClient.java
+++
b/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/RestClient.java
@@ -8020,7 +8020,7 @@ public class RestClient extends MarshallingContextable
implements HttpClient, Cl
if (parsers.isEmpty())
return null;
if (nn(mediaType)) {
- var p = parsers.getParser(mediaType);
+ var p = parsers.getParser(mediaType).orElse(null);
if (nn(p))
return p;
}
@@ -8037,7 +8037,7 @@ public class RestClient extends MarshallingContextable
implements HttpClient, Cl
if (serializers.isEmpty())
return null;
if (nn(mediaType)) {
- var s = serializers.getSerializer(mediaType);
+ var s =
serializers.getSerializer(mediaType).orElse(null);
if (nn(s))
return s;
}
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java
index 8256852f5b..e03ae16f5a 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java
@@ -21,6 +21,7 @@ import static
org.apache.juneau.commons.utils.AssertionUtils.*;
import java.io.*;
import java.nio.charset.*;
+import org.apache.juneau.http.response.*;
import org.apache.juneau.marshall.parser.*;
import org.apache.juneau.marshall.stream.*;
@@ -128,8 +129,9 @@ public final class ResponseBody {
*
* <p>
* Equivalent to {@link #asCursor(Parser, Class)
asCursor(negotiatedParser, type)} where the parser is selected
- * from the response {@code Content-Type} header (defaulting to JSON
when no match is found); use
- * {@link #asCursor(Parser, Class)} to read with an explicit parser.
+ * from the response {@code Content-Type} header; when no registered
parser matches and no default parser is
+ * configured, a <c>415 Unsupported Media Type</c> is thrown. Use
{@link #asCursor(Parser, Class)} to read with an
+ * explicit parser.
*
* @param <T> The cursor type ({@link RecordReader}, {@link
TokenReader}, or a concrete subtype).
* @param type The declared cursor type. Must not be <jk>null</jk>.
@@ -142,11 +144,12 @@ public final class ResponseBody {
}
/**
- * Parses the response body to {@code type} using the parser negotiated
from the response {@code Content-Type}
- * (defaulting to JSON when the header is absent, or the client has no
matching/configured parser).
+ * Parses the response body to {@code type} using the parser negotiated
from the response {@code Content-Type}.
*
* <p>
- * Use {@link #as(Parser, Class)} to force a specific parser, bypassing
content negotiation.
+ * When the header is absent or matches no registered parser and no
default parser is configured, a
+ * <c>415 Unsupported Media Type</c> is thrown. Use {@link #as(Parser,
Class)} to force a specific parser,
+ * bypassing content negotiation.
*
* @param <T> The type to parse to.
* @param type The type to parse to. Must not be <jk>null</jk>.
@@ -188,7 +191,9 @@ public final class ResponseBody {
private Parser negotiatedParser() {
var h = response.getFirstHeader("Content-Type");
- return response.getClient().getMatchingParser(h == null ? null
: h.value());
+ var ct = h == null ? null : h.value();
+ return
response.getClient().getMatchingParser(ct).orElseThrow(() -> new
UnsupportedMediaType(
+ "No parser matched the response Content-Type ''{0}''
and no default parser is configured on the client.", ct));
}
/**
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 94621b24f4..7c6ce46742 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest.client;
import static org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
import java.io.*;
import java.util.*;
@@ -30,7 +31,6 @@ import org.apache.juneau.http.entity.*;
import org.apache.juneau.http.header.*;
import org.apache.juneau.http.part.*;
import org.apache.juneau.http.remote.*;
-import org.apache.juneau.marshall.json.*;
import org.apache.juneau.marshall.parser.*;
import org.apache.juneau.marshall.serializer.*;
import org.apache.juneau.rest.client.remote.*;
@@ -218,7 +218,12 @@ public final class RestClient implements Closeable {
}
/**
- * Returns the serializer used for outbound bodies when the format is
not otherwise discernable.
+ * Returns the explicitly-configured default serializer used for
outbound bodies when the format is not otherwise
+ * discernable.
+ *
+ * <p>
+ * Only an explicit default set via {@link
Builder#defaultSerializer(Serializer)} is honored — there is no implicit
+ * JSON fallback and no lone-registered-entry fallback. A
fully-unconfigured client returns {@link Optional#empty()}.
*
* <p>
* <b>Beta — API subject to change:</b> This type is part of the
next-generation REST client and HTTP stack
@@ -226,18 +231,20 @@ public final class RestClient implements Closeable {
* It is not API-frozen: binary- and source-incompatible changes may
appear in the <b>next major</b> Juneau release
* (and possibly earlier).
*
- * @return The default serializer. Never <jk>null</jk>.
+ * @return The configured default serializer, or {@link
Optional#empty()} if none was set.
*/
- public Serializer getDefaultSerializer() {
- if (defaultSerializer != null)
- return defaultSerializer;
- if (serializers != null && ! serializers.isEmpty())
- return serializers.getSerializers().get(0);
- return JsonSerializer.DEFAULT;
+ public Optional<Serializer> getDefaultSerializer() {
+ return opt(defaultSerializer);
}
/**
- * Returns the parser matching the given response {@code Content-Type},
falling back to the default/JSON.
+ * Returns the parser matching the given response {@code Content-Type},
or the explicitly-configured default parser.
+ *
+ * <p>
+ * Resolution precedence: an exact media-type match against the
client's {@link ParserSet} wins; otherwise the
+ * explicitly-configured {@link Builder#defaultParser(Parser) default
parser} is used; otherwise
+ * {@link Optional#empty()} is returned. There is no implicit JSON
fallback and no lone-registered-entry fallback,
+ * so a fully-unconfigured client resolves to empty (callers throw
<c>415 Unsupported Media Type</c>).
*
* <p>
* <b>Beta — API subject to change:</b> This type is part of the
next-generation REST client and HTTP stack
@@ -246,62 +253,58 @@ public final class RestClient implements Closeable {
* (and possibly earlier).
*
* @param contentType The response {@code Content-Type} header value.
May be <jk>null</jk>.
- * @return The matching parser. Never <jk>null</jk>.
+ * @return The matching parser, or {@link Optional#empty()} if nothing
matched and no default parser is configured.
*/
- public Parser getMatchingParser(String contentType) {
+ public Optional<Parser> getMatchingParser(String contentType) {
if (parsers != null && contentType != null) {
var p = parsers.getParser(contentType);
- if (p != null)
+ if (p.isPresent())
return p;
}
- if (defaultParser != null)
- return defaultParser;
- if (parsers != null && ! parsers.isEmpty())
- return parsers.getParsers().get(0);
- return JsonParser.DEFAULT;
+ return opt(defaultParser);
}
/**
- * Returns the registered request serializer matching the given media
type, or <jk>null</jk> if none matches.
+ * Returns the registered request serializer matching the given media
type, or {@link Optional#empty()} if none matches.
*
* <p>
* Unlike {@link #getDefaultSerializer()}, this method performs
media-type-driven <i>selection</i> against the
- * client's {@link SerializerSet} (via {@link
SerializerSet#getSerializer(String)}) and returns <jk>null</jk> on no
- * match so the caller can apply the locked no-match fallback (use the
default serializer but still send the
- * overridden {@code Content-Type} label). An explicitly-configured
single default serializer is also consulted
- * when no set is registered.
+ * client's {@link SerializerSet} (via {@link
SerializerSet#getSerializer(String)}) and returns
+ * {@link Optional#empty()} on no match so the caller can apply the
locked no-match fallback (use the default
+ * serializer but still send the overridden {@code Content-Type}
label). An explicitly-configured single default
+ * serializer is also consulted when no set is registered.
*
* <p>
* <b>Beta — API subject to change:</b> This type is part of the
next-generation REST client and HTTP stack
* ({@code org.apache.juneau.marshall.ng.*}).
*
* @param mediaType The desired request media type. May be
<jk>null</jk>/empty.
- * @return The matching serializer, or <jk>null</jk> if no registered
serializer matches.
+ * @return The matching serializer, or {@link Optional#empty()} if no
registered serializer matches.
*/
- public Serializer getSerializerForMediaType(String mediaType) {
+ public Optional<Serializer> getSerializerForMediaType(String mediaType)
{
if (mediaType == null || mediaType.isEmpty() || serializers ==
null)
- return null;
+ return opte();
return serializers.getSerializer(mediaType);
}
/**
- * Returns the registered parser matching the given media type, or
<jk>null</jk> if none matches.
+ * Returns the registered parser matching the given media type, or
{@link Optional#empty()} if none matches.
*
* <p>
* Used by the next-gen {@code RemoteClient} as the {@code accept}
fallback parser: it is consulted only when the
* response {@code Content-Type} matched no registered parser (or the
response was unlabeled). Returns
- * <jk>null</jk> on no match so the caller can fall back to the default
parser.
+ * {@link Optional#empty()} on no match so the caller can fall back to
the default parser.
*
* <p>
* <b>Beta — API subject to change:</b> This type is part of the
next-generation REST client and HTTP stack
* ({@code org.apache.juneau.marshall.ng.*}).
*
* @param mediaType The desired parse media type. May be
<jk>null</jk>/empty.
- * @return The matching parser, or <jk>null</jk> if no registered
parser matches.
+ * @return The matching parser, or {@link Optional#empty()} if no
registered parser matches.
*/
- public Parser getParserForMediaType(String mediaType) {
+ public Optional<Parser> getParserForMediaType(String mediaType) {
if (mediaType == null || mediaType.isEmpty() || parsers == null)
- return null;
+ return opte();
return parsers.getParser(mediaType);
}
@@ -314,7 +317,8 @@ public final class RestClient implements Closeable {
* It is not API-frozen: binary- and source-incompatible changes may
appear in the <b>next major</b> Juneau release
* (and possibly earlier).
*
- * @return The default {@code Accept} header value. Never <jk>null</jk>.
+ * @return The default {@code Accept} header value, or <jk>null</jk> if
no parsers and no default parser are
+ * configured (no implicit {@code application/json} fallback).
*/
public String getDefaultAccept() {
if (parsers != null) {
@@ -327,7 +331,7 @@ public final class RestClient implements Closeable {
if (! mts.isEmpty())
return
mts.stream().map(MediaType::toString).collect(Collectors.joining(", "));
}
- return "application/json";
+ return null;
}
/**
@@ -567,7 +571,14 @@ public final class RestClient implements Closeable {
}
/**
- * Designates the default serializer used when the outbound
format is not otherwise discernable.
+ * Designates the default serializer used for outbound bodies
when no registered serializer matches the
+ * requested media type.
+ *
+ * <p>
+ * This is the explicit, opt-in way to restore a fallback
serializer. Without it, a request that cannot be
+ * matched to a registered serializer (and any body requiring
serialization on a serializer-less client) fails
+ * rather than silently defaulting to JSON. Set {@code
defaultSerializer(JsonSerializer.DEFAULT)} to recover
+ * the pre-10.0 implicit-JSON behavior.
*
* @param value The default serializer. May be <jk>null</jk>.
* @return This object.
@@ -578,7 +589,14 @@ public final class RestClient implements Closeable {
}
/**
- * Designates the default parser used when the response {@code
Content-Type} is absent or unmatched.
+ * Designates the default parser used when the response {@code
Content-Type} is absent or matches no registered
+ * parser.
+ *
+ * <p>
+ * This is the explicit, opt-in way to restore a fallback
parser. Without it, a response whose
+ * {@code Content-Type} matches no registered parser fails with
<c>415 Unsupported Media Type</c> rather than
+ * silently parsing as JSON. Set {@code
defaultParser(JsonParser.DEFAULT)} to recover the pre-10.0
+ * implicit-JSON behavior.
*
* @param value The default parser. May be <jk>null</jk>.
* @return This object.
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
index 85e76309c3..ba098126dd 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
@@ -244,12 +244,15 @@ public final class RestRequest {
* Custom converters can be registered on the builder.
*
* <p>
- * If no converter matches, the object is serialized with the client's
default serializer
- * ({@link RestClient#getDefaultSerializer()}) and sent as a string
body using the serializer's content type.
+ * If no converter matches, the object is serialized with the client's
explicitly-configured default serializer
+ * ({@link RestClient#getDefaultSerializer()}) and sent as a string
body using the serializer's content type. When
+ * no default serializer is configured, an {@link
IllegalStateException} is raised (there is no implicit JSON
+ * fallback) — configure one via {@link
RestClient.Builder#defaultSerializer(org.apache.juneau.marshall.serializer.Serializer)}.
*
* @param value The body object. May be <jk>null</jk> to clear the body.
* @return This object.
* @throws IOException If a converter fails or the default serializer
fails.
+ * @throws IllegalStateException If no converter matches and no default
serializer is configured on the client.
* @throws IllegalArgumentException If the default serializer produces
output that is neither text nor {@code byte[]}.
* Binary ({@code byte[]}) serializer output is sent as a binary
body using the serializer's media type
* (falling back to {@code application/octet-stream} when the
media type is <jk>null</jk>).
@@ -271,7 +274,8 @@ public final class RestRequest {
// Mirrors classic SerializedEntity — the serializer writes
straight to the transport output stream during
// run() rather than being pre-materialized into a
String/byte[]. Repeatable (re-serializes on resend), so a
// future auto-retry can resend it; streaming bodies
(InputStream/Reader) remain non-repeatable.
- var s = client.getDefaultSerializer();
+ var s = client.getDefaultSerializer().orElseThrow(() -> new
IllegalStateException(
+ "No default serializer is configured on the client.
Configure one via RestClient.Builder.defaultSerializer(...)."));
var mt = s.getResponseContentType();
body = SerializerBody.of(s, value, mt != null ? mt.toString() :
"application/json");
convertedBody = null;
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteClient.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteClient.java
index 818597d01e..1d77235375 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteClient.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteClient.java
@@ -265,21 +265,22 @@ public final class RemoteClient {
*
* <p>
* When a {@code contentType} media type is in effect, the
matching registered request serializer is selected
- * from the client's serializer set ({@link
RestClient#getSerializerForMediaType(String)}). Per the locked
- * no-match fallback, if no registered serializer matches, the
client's default serializer is used to write the
- * body but the overridden media type is still sent as the
{@code Content-Type} label (supporting vendor media
- * types such as {@code application/vnd.foo+json} whose bytes
are really the default format).
+ * from the client's serializer set ({@link
RestClient#getSerializerForMediaType(String)}). If no registered
+ * serializer matches, the client's explicitly-configured
default serializer is used to write the body but the
+ * overridden media type is still sent as the {@code
Content-Type} label (supporting vendor media types such as
+ * {@code application/vnd.foo+json} whose bytes are really the
default format). If neither a match nor a default
+ * serializer is available, an {@link IllegalStateException} is
raised (there is no implicit JSON fallback).
*
* @param methodMeta The method metadata.
* @return The resolved body format; {@link BodyFormat#NONE}
when no {@code contentType} attribute is in effect.
+ * @throws IllegalStateException If no serializer matches and
no default serializer is configured on the client.
*/
private BodyFormat resolveBodyFormat(RrpcInterfaceMethodMeta
methodMeta) {
var contentType =
firstNonEmpty(methodMeta.getContentType(), meta.getContentType());
if (isEmpty(contentType))
return BodyFormat.NONE;
- var serializer =
client.getSerializerForMediaType(contentType);
- if (serializer == null)
- serializer = client.getDefaultSerializer(); //
No-match fallback: default bytes, overridden label.
+ var serializer =
client.getSerializerForMediaType(contentType).or(client::getDefaultSerializer).orElseThrow(()
->
+ new IllegalStateException("No serializer
matched Content-Type '" + contentType + "' and no default serializer is
configured on the client. Configure one via
RestClient.Builder.defaultSerializer(...)."));
return new BodyFormat(serializer, contentType);
}
@@ -1171,7 +1172,8 @@ public final class RemoteClient {
if (isScalarPart(arg))
return StringBody.of(arg.toString(),
firstNonEmpty(contentType, "text/plain; charset=UTF-8"));
// Bean part: stream it through the client's default
serializer (no full in-memory materialization).
- var s = client.getDefaultSerializer();
+ var s = client.getDefaultSerializer().orElseThrow(() ->
new IllegalStateException(
+ "No default serializer is configured on the
client. Configure one via RestClient.Builder.defaultSerializer(...)."));
return contentType != null ? SerializerBody.of(s, arg,
contentType) : SerializerBody.of(s, arg);
}
@@ -1249,25 +1251,29 @@ public final class RemoteClient {
* <li>If the response {@code Content-Type} matches a
registered parser, use it.
* <li>Otherwise (response unlabeled or its type matched
nothing), use the parser matching the {@code accept}
* media type, if one is registered.
- * <li>Otherwise fall back to the client default parser
({@link RestClient#getMatchingParser(String)}).
+ * <li>Otherwise fall back to the client's
explicitly-configured default parser
+ * ({@link RestClient#getMatchingParser(String)});
if none is configured this throws
+ * <c>415 Unsupported Media Type</c> (there is no
implicit JSON fallback).
* </ol>
*
* @param resp The response (source of the client + parsers).
* @param responseContentType The response {@code Content-Type}
header value. May be <jk>null</jk>.
* @param acceptFallback The {@code accept} media type
fallback. May be <jk>null</jk>/empty.
* @return The parser to use. Never <jk>null</jk>.
+ * @throws UnsupportedMediaType If no parser matches and no
default parser is configured on the client.
*/
private static Parser selectParser(RestResponse resp, String
responseContentType, String acceptFallback) {
var c = resp.getClient();
var p = c.getParserForMediaType(responseContentType);
- if (p != null)
- return p;
+ if (p.isPresent())
+ return p.get();
if (isNotEmpty(acceptFallback)) {
var ap =
c.getParserForMediaType(acceptFallback);
- if (ap != null)
- return ap;
+ if (ap.isPresent())
+ return ap.get();
}
- return c.getMatchingParser(responseContentType);
+ return
c.getMatchingParser(responseContentType).orElseThrow(() -> new
UnsupportedMediaType(
+ "No parser matched the response Content-Type
''{0}'' and no default parser is configured on the client.",
responseContentType));
}
private static String combinePaths(String base, String method) {
diff --git
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
index 45bc9a795d..d69c5fe0af 100644
---
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
+++
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
@@ -48,7 +48,9 @@ class ResponseBody_Cursor_Test {
.header("Content-Type", "application/json")
.body(new
ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)))
.build();
- return new RestResponse(tr, RestClient.create());
+ // Register a JSON parser so the negotiated-parser cursor paths
resolve from the application/json Content-Type
+ // (there is no implicit JSON fallback as of 10.0.0).
+ return new RestResponse(tr,
RestClient.builder().parser(JsonParser.DEFAULT).build());
}
//
==========================================================================
diff --git
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
index cef31d6095..ff6ea3a05d 100644
---
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
+++
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
@@ -24,15 +24,24 @@ import org.apache.juneau.marshall.parser.*;
import org.apache.juneau.marshall.serializer.*;
import org.junit.jupiter.api.*;
+/**
+ * Content-negotiation resolver contract for the next-gen {@link RestClient}.
+ *
+ * <p>
+ * As of 10.0.0 the implicit JSON fallback and the lone-registered-entry
fallback are removed: a serializer/parser is
+ * resolved only by an exact media-type match or by an explicitly-configured
{@code defaultSerializer(...)} /
+ * {@code defaultParser(...)}; otherwise the resolver is genuinely empty (and
callers throw 415 / a client-side error).
+ */
class RestClient_Negotiation_Test {
@Test
- void a01_unconfigured_defaultsToJson() throws Exception {
+ void a01_unconfigured_resolvesEmpty() throws Exception {
+ // No serializers/parsers and no explicit defaults — everything
resolves empty (no implicit JSON).
try (var c = RestClient.create()) {
- assertSame(JsonSerializer.DEFAULT,
c.getDefaultSerializer());
- assertSame(JsonParser.DEFAULT,
c.getMatchingParser(null));
- assertSame(JsonParser.DEFAULT,
c.getMatchingParser("application/jsonl"));
- assertEquals("application/json", c.getDefaultAccept());
+ assertTrue(c.getDefaultSerializer().isEmpty());
+ assertTrue(c.getMatchingParser(null).isEmpty());
+
assertTrue(c.getMatchingParser("application/jsonl").isEmpty());
+ assertNull(c.getDefaultAccept());
}
}
@@ -41,17 +50,18 @@ class RestClient_Negotiation_Test {
var sset = SerializerSet.create().add(JsonSerializer.DEFAULT,
JsonlSerializer.DEFAULT).build();
var pset = ParserSet.create().add(JsonParser.DEFAULT,
JsonlParser.DEFAULT).build();
try (var c =
RestClient.builder().serializers(sset).parsers(pset).build()) {
- assertInstanceOf(JsonlParser.class,
c.getMatchingParser("application/jsonl"));
- assertInstanceOf(JsonParser.class,
c.getMatchingParser("application/json"));
- assertSame(JsonSerializer.DEFAULT,
c.getDefaultSerializer());
- assertInstanceOf(JsonParser.class,
c.getMatchingParser("text/unknown"));
+ assertInstanceOf(JsonlParser.class,
c.getMatchingParser("application/jsonl").orElseThrow());
+ assertInstanceOf(JsonParser.class,
c.getMatchingParser("application/json").orElseThrow());
+ // A registered set is NOT a default — no implicit
first-entry/JSON fallback.
+ assertTrue(c.getDefaultSerializer().isEmpty());
+
assertTrue(c.getMatchingParser("text/unknown").isEmpty());
assertTrue(c.getDefaultAccept().contains("application/json"));
assertTrue(c.getDefaultAccept().contains("application/jsonl"));
}
}
@Test
- void a03_explicitDefaultOverridesFirstInSet() throws Exception {
+ void a03_explicitDefaultIsUsedForUnmatched() throws Exception {
var sset = SerializerSet.create().add(JsonSerializer.DEFAULT,
JsonlSerializer.DEFAULT).build();
var pset = ParserSet.create().add(JsonParser.DEFAULT,
JsonlParser.DEFAULT).build();
try (var c = RestClient.builder()
@@ -59,20 +69,23 @@ class RestClient_Negotiation_Test {
.defaultSerializer(JsonlSerializer.DEFAULT)
.defaultParser(JsonlParser.DEFAULT)
.build()) {
- assertSame(JsonlSerializer.DEFAULT,
c.getDefaultSerializer());
- assertSame(JsonlParser.DEFAULT,
c.getMatchingParser("text/unknown"));
- assertInstanceOf(JsonParser.class,
c.getMatchingParser("application/json"));
+ assertSame(JsonlSerializer.DEFAULT,
c.getDefaultSerializer().orElseThrow());
+ assertSame(JsonlParser.DEFAULT,
c.getMatchingParser("text/unknown").orElseThrow());
+ // Exact match still wins over the configured default.
+ assertInstanceOf(JsonParser.class,
c.getMatchingParser("application/json").orElseThrow());
}
}
@Test
- void a04_appendConvenience() throws Exception {
+ void a04_appendConvenience_isNotADefault() throws Exception {
+ // Appending serializers/parsers registers them in the set but
does NOT make them the default.
try (var c = RestClient.builder()
.serializer(JsonlSerializer.DEFAULT)
.parser(JsonlParser.DEFAULT)
.build()) {
- assertSame(JsonlSerializer.DEFAULT,
c.getDefaultSerializer());
- assertInstanceOf(JsonlParser.class,
c.getMatchingParser("application/jsonl"));
+ assertTrue(c.getDefaultSerializer().isEmpty());
+ assertInstanceOf(JsonlParser.class,
c.getMatchingParser("application/jsonl").orElseThrow());
+
assertTrue(c.getMatchingParser("text/unknown").isEmpty());
}
}
@@ -82,8 +95,8 @@ class RestClient_Negotiation_Test {
.defaultSerializer(JsonlSerializer.DEFAULT)
.defaultParser(JsonlParser.DEFAULT)
.build()) {
- assertSame(JsonlSerializer.DEFAULT,
c.getDefaultSerializer());
- assertSame(JsonlParser.DEFAULT,
c.getMatchingParser("text/unknown"));
+ assertSame(JsonlSerializer.DEFAULT,
c.getDefaultSerializer().orElseThrow());
+ assertSame(JsonlParser.DEFAULT,
c.getMatchingParser("text/unknown").orElseThrow());
}
}
@@ -95,15 +108,6 @@ class RestClient_Negotiation_Test {
}
}
- @Test
- void a08_loneDefaultParserAdvertisesItsMediaTypes() throws Exception {
- try (var c =
RestClient.builder().defaultParser(JsonlParser.DEFAULT).build()) {
- var accept = c.getDefaultAccept();
- assertTrue(accept.startsWith("application/jsonl"), ()
-> "Default Accept was: " + accept);
- assertNotEquals("application/json", accept, () ->
"Default Accept was: " + accept);
- }
- }
-
@Test
void a07_explicitSetWinsOverAppend() throws Exception {
var sset =
SerializerSet.create().add(JsonSerializer.DEFAULT).build();
@@ -113,10 +117,41 @@ class RestClient_Negotiation_Test {
.serializer(JsonlSerializer.DEFAULT)
.parser(JsonlParser.DEFAULT)
.build()) {
- assertSame(JsonSerializer.DEFAULT,
c.getDefaultSerializer());
- assertInstanceOf(JsonParser.class,
c.getMatchingParser("text/unknown"));
- assertInstanceOf(JsonParser.class,
c.getMatchingParser("application/jsonl"));
+ // The appended jsonl entries are ignored because an
explicit set was supplied.
+ assertInstanceOf(JsonParser.class,
c.getMatchingParser("application/json").orElseThrow());
+
assertTrue(c.getMatchingParser("application/jsonl").isEmpty());
+
assertTrue(c.getMatchingParser("text/unknown").isEmpty());
+ assertTrue(c.getDefaultSerializer().isEmpty());
assertFalse(c.getDefaultAccept().contains("jsonl"));
}
}
+
+ @Test
+ void a08_loneDefaultParserAdvertisesItsMediaTypes() throws Exception {
+ try (var c =
RestClient.builder().defaultParser(JsonlParser.DEFAULT).build()) {
+ var accept = c.getDefaultAccept();
+ assertTrue(accept.startsWith("application/jsonl"), ()
-> "Default Accept was: " + accept);
+ assertNotEquals("application/json", accept, () ->
"Default Accept was: " + accept);
+ }
+ }
+
+ @Test
+ void a09_exactMatchWinsOverExplicitDefault() throws Exception {
+ var pset = ParserSet.create().add(JsonParser.DEFAULT,
JsonlParser.DEFAULT).build();
+ try (var c =
RestClient.builder().parsers(pset).defaultParser(JsonParser.DEFAULT).build()) {
+ // Exact match on the registered set takes precedence
over the explicit default.
+ assertInstanceOf(JsonlParser.class,
c.getMatchingParser("application/jsonl").orElseThrow());
+ // Unmatched falls back to the explicit default.
+ assertSame(JsonParser.DEFAULT,
c.getMatchingParser("text/unknown").orElseThrow());
+ }
+ }
+
+ @Test
+ void a10_defaultSerializerOptInRestoresFallback() throws Exception {
+ try (var unconfigured = RestClient.create();
+ var configured =
RestClient.builder().defaultSerializer(JsonSerializer.DEFAULT).build()) {
+
assertTrue(unconfigured.getDefaultSerializer().isEmpty());
+ assertSame(JsonSerializer.DEFAULT,
configured.getDefaultSerializer().orElseThrow());
+ }
+ }
}
diff --git
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
index 6e2626a27f..c81477f149 100644
---
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
+++
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
@@ -25,6 +25,7 @@ import java.util.concurrent.*;
import org.apache.juneau.commons.inject.*;
import org.apache.juneau.marshall.*;
+import org.apache.juneau.marshall.json.*;
import org.apache.juneau.rest.client.*;
import org.apache.juneau.rest.client.RestRequest;
import org.apache.juneau.rest.server.*;
@@ -271,6 +272,7 @@ public final class MockRestClient implements Closeable {
var ngClient = RestClient.builder()
.transport(transport)
.rootUrl("http://localhost" +
fullContextPath)
+
.defaultSerializer(JsonSerializer.DEFAULT)
.build();
return new MockRestClient(ngClient);
diff --git
a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/NextGenContentNegotiation_Test.java
b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/NextGenContentNegotiation_Test.java
index dbba7c281a..883f3e2062 100644
---
a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/NextGenContentNegotiation_Test.java
+++
b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/NextGenContentNegotiation_Test.java
@@ -84,7 +84,8 @@ class NextGenContentNegotiation_Test {
void a01_outboundContentTypeFromDefaultSerializer() throws Exception {
var sset =
SerializerSet.create().add(JsonlSerializer.DEFAULT).build();
try (var mock = MockRestClient.create(JsonlServer.class)) {
- try (var nc =
RestClient.builder().transport(mock.getClient().getTransport()).serializers(sset).build())
{
+ // A registered set is no longer an implicit default —
designate the default serializer explicitly.
+ try (var nc =
RestClient.builder().transport(mock.getClient().getTransport()).serializers(sset).defaultSerializer(JsonlSerializer.DEFAULT).build())
{
var rawBody =
nc.post("/echoContentType").body(new Bean("a", 1)).run().body().asString();
var echoed = JsonParser.DEFAULT.parse(rawBody,
String.class);
assertTrue(echoed.startsWith("application/jsonl"), () -> "Echoed Content-Type
was: " + echoed);
diff --git
a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RemoteCursorBinding_NextGen_Test.java
b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RemoteCursorBinding_NextGen_Test.java
index f21aff0dcb..47ec943ae4 100644
---
a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RemoteCursorBinding_NextGen_Test.java
+++
b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RemoteCursorBinding_NextGen_Test.java
@@ -35,8 +35,8 @@ import org.junit.jupiter.api.*;
* <p>
* Verifies that {@code RecordReader} / {@code TokenReader} (and concrete
subtypes like {@code JsonTokenReader}) work
* as {@code @RemoteOp} return types, and that {@code RecordStreamBody} works
as a {@code @Content} parameter and as a
- * directly-set request body. The next-generation client performs no {@code
Content-Type} negotiation, so cursors use
- * the default JSON marshaller.
+ * directly-set request body. Each test builds a next-generation client
configured with a JSON parser so the
+ * inbound cursor parser is negotiated from the response {@code Content-Type}.
*/
class RemoteCursorBinding_NextGen_Test {
@@ -84,8 +84,9 @@ class RemoteCursorBinding_NextGen_Test {
"resource" // Inner client returned by getClient() is owned by
the MockRestClient, not closed separately.
})
void a01_recordReaderReturnType() throws Exception {
- try (var client = MockRestClient.create(JsonServer.class)) {
- var api =
client.getClient().remote(JsonClientApi.class);
+ try (var client = MockRestClient.create(JsonServer.class);
+ var nc =
RestClient.builder().transport(client.getClient().getTransport()).parser(JsonParser.DEFAULT).build())
{
+ var api = nc.remote(JsonClientApi.class);
try (RecordReader r = api.getBean()) {
var b = r.read(Bean.class);
assertEquals("alice", b.name);
@@ -99,8 +100,9 @@ class RemoteCursorBinding_NextGen_Test {
"resource" // Inner client returned by getClient() is owned by
the MockRestClient, not closed separately.
})
void a02_tokenReaderReturnType() throws Exception {
- try (var client = MockRestClient.create(JsonServer.class)) {
- var api =
client.getClient().remote(JsonClientApi.class);
+ try (var client = MockRestClient.create(JsonServer.class);
+ var nc =
RestClient.builder().transport(client.getClient().getTransport()).parser(JsonParser.DEFAULT).build())
{
+ var api = nc.remote(JsonClientApi.class);
try (TokenReader r = api.getBeanAsTokens()) {
var b = r.read(Bean.class);
assertEquals("alice", b.name);
@@ -114,8 +116,9 @@ class RemoteCursorBinding_NextGen_Test {
"resource" // Inner client returned by getClient() is owned by
the MockRestClient, not closed separately.
})
void a03_concreteCursorReturnType() throws Exception {
- try (var client = MockRestClient.create(JsonServer.class)) {
- var api =
client.getClient().remote(JsonClientApi.class);
+ try (var client = MockRestClient.create(JsonServer.class);
+ var nc =
RestClient.builder().transport(client.getClient().getTransport()).parser(JsonParser.DEFAULT).build())
{
+ var api = nc.remote(JsonClientApi.class);
try (JsonTokenReader r = api.getBeanAsJsonTokens()) {
var b = r.read(Bean.class);
assertEquals("alice", b.name);
@@ -132,8 +135,9 @@ class RemoteCursorBinding_NextGen_Test {
"resource" // Fluent writer calls return the caller-owned
writer for chaining; nothing new to close.
})
void b01_recordStreamBody_record() throws Exception {
- try (var client = MockRestClient.create(JsonServer.class)) {
- var api =
client.getClient().remote(JsonClientApi.class);
+ try (var client = MockRestClient.create(JsonServer.class);
+ var nc =
RestClient.builder().transport(client.getClient().getTransport()).parser(JsonParser.DEFAULT).build())
{
+ var api = nc.remote(JsonClientApi.class);
Bean got = api.echo(RecordStreamBody.records(w -> {
try {
w.write(new Bean("dave", 99));
@@ -151,8 +155,9 @@ class RemoteCursorBinding_NextGen_Test {
"resource" // Fluent writer calls return the caller-owned
writer for chaining; nothing new to close.
})
void b02_recordStreamBody_token() throws Exception {
- try (var client = MockRestClient.create(JsonServer.class)) {
- var api =
client.getClient().remote(JsonClientApi.class);
+ try (var client = MockRestClient.create(JsonServer.class);
+ var nc =
RestClient.builder().transport(client.getClient().getTransport()).parser(JsonParser.DEFAULT).build())
{
+ var api = nc.remote(JsonClientApi.class);
Bean got = api.echo(RecordStreamBody.token(w -> {
try {
w.startObject();
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
index eb66db0a2a..6269e26321 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
@@ -491,7 +491,7 @@ public class RestResponse extends
HttpServletResponseWrapper {
if (nn(serializer)) {
serializerMatch = opt(new
SerializerMatch(getMediaType(), serializer));
} else {
- serializerMatch =
opt(opContext.getSerializers().getSerializerMatch(request.getHeaderParam("Accept").orElse("*/*")));
+ serializerMatch =
opContext.getSerializers().getSerializerMatch(request.getHeaderParam("Accept").orElse("*/*"));
}
return serializerMatch;
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestContent.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestContent.java
index 88cd167264..06540661b1 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestContent.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestContent.java
@@ -397,7 +397,7 @@ public class RequestContent {
if (nn(mediaType) && nn(parser))
return opt(new ParserMatch(mediaType, parser));
var mt = getMediaType();
- return opt(mt).map(x -> parsers.getParserMatch(x));
+ return opt(mt).flatMap(parsers::getParserMatch);
}
/**
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
index 9f6b43e304..78e2142f31 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
@@ -733,7 +733,7 @@ public class BasicSwaggerProviderSession {
for (var mt : mediaTypes) {
if (mt != MediaType.HTML) {
- var s2 = sm.getSerializers().getSerializer(mt);
+ var s2 =
sm.getSerializers().getSerializer(mt).orElse(null);
if (nn(s2)) {
try {
// @formatter:off
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/vars/SerializedRequestAttrVar.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/vars/SerializedRequestAttrVar.java
index 7ff76d1b8e..fb67b929ca 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/vars/SerializedRequestAttrVar.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/vars/SerializedRequestAttrVar.java
@@ -69,7 +69,7 @@ public class SerializedRequestAttrVar extends StreamedVar {
var s2 = splita(key);
var req =
session.getBean(RestRequest.class).orElseThrow(InternalServerError::new);
var o = req.getAttribute(key).orElse(key);
- Serializer s =
req.getOpContext().getSerializers().getSerializer(s2[0]);
+ Serializer s =
req.getOpContext().getSerializers().getSerializer(s2[0]).orElse(null);
if (nn(s))
s.serialize(w, o);
}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/vars/RestServerVars_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/vars/RestServerVars_Test.java
index a4646f43d4..7358faf609 100644
---
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/vars/RestServerVars_Test.java
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/vars/RestServerVars_Test.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.io.*;
+import java.util.*;
import org.junit.jupiter.api.*;
import org.apache.juneau.*;
@@ -376,7 +377,7 @@ class RestServerVars_Test extends TestBase {
@Test void
e05_serializedRequestAttrVar_resolveTo_serializerNotFound_noOutput() throws
Exception {
var req = mock(RestRequest.class, RETURNS_DEEP_STUBS);
when(req.getAttribute(any())).thenReturn(mock(RequestAttribute.class));
-
when(req.getOpContext().getSerializers().getSerializer(any(String.class))).thenReturn(null);
+
when(req.getOpContext().getSerializers().getSerializer(any(String.class))).thenReturn(opte());
var w = new StringWriter();
new SerializedRequestAttrVar().resolveTo(sessionWith(req), w,
"text/plain,myKey");
assertEquals("", w.toString());
@@ -392,7 +393,7 @@ class RestServerVars_Test extends TestBase {
var attr = mock(RequestAttribute.class);
when(attr.orElse(any())).thenReturn(outputSb);
when(req.getAttribute(any())).thenReturn(attr);
-
when(req.getOpContext().getSerializers().getSerializer(any(String.class))).thenReturn(Json5Serializer.DEFAULT);
+
when(req.getOpContext().getSerializers().getSerializer(any(String.class))).thenReturn(Optional.of(Json5Serializer.DEFAULT));
new SerializedRequestAttrVar().resolveTo(sessionWith(req), new
StringWriter(), "application/json,myKey");
assertFalse(outputSb.toString().isEmpty());
}