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 763c6aaec0 Fix deferred code bugs surfaced during Javadoc audits
(TODO-270)
763c6aaec0 is described below
commit 763c6aaec090d78415f009af096c6e2e11fca97d
Author: James Bognar <[email protected]>
AuthorDate: Mon Jul 20 10:50:58 2026 -0400
Fix deferred code bugs surfaced during Javadoc audits (TODO-270)
Category A (clear bugs): 3-arg serialize-only swap() overloads no longer
always-throw; prototext/toml tokenizer printf-injection hardening;
JsonSchema.getAdditionalProperties() reads the correct backing field;
Config import statements implemented (ConfigMap.setImport/removeImport) +
Config.setImport honors its sectionName param; SwaggerException single
format pass; FluentRequestHeaderAssertion propagates its creator;
CookieList null-coalesces null getCookies().
Category B (per maintainer resolutions): null-guard null-hostile lookup
getters (RrpcInterfaceMethodMeta, DebugConfig, BeanRegistry, ClassMeta.same,
ResourceDescription hashCode/compareTo); null-coalesce addDefault(...) and
ContentSecurityPolicy.directive varargs; set/get null-contract parity
(HttpPartSchema enum setters, Iso8601Utils, McpServerConfig,
RemoteOperationArg);
refresh stale Javadoc examples (AtomBuilder, MenuItemWidget,
BasicDebugEnablement);
honor previously-ignored extension params
(MarshallingTraverseSession.onError,
Json5List.ofString, MarshallingSession.toBeanMap PropertyNamer).
Co-authored-by: Cursor <[email protected]>
---
.../org/apache/juneau/bean/atom/AtomBuilder.java | 8 +-
.../apache/juneau/bean/jsonschema/JsonSchema.java | 2 +-
.../juneau/bean/jsonschema/JsonSchema_Test.java | 9 ++
.../org/apache/juneau/commons/bean/BeanMeta.java | 66 +++++++++++++--
.../main/java/org/apache/juneau/config/Config.java | 4 +-
.../apache/juneau/config/internal/ConfigMap.java | 37 +++++++--
.../java/org/apache/juneau/config/Config_Test.java | 39 +++++++--
.../org/apache/juneau/marshall/BeanRegistry.java | 4 +-
.../java/org/apache/juneau/marshall/ClassMeta.java | 25 +++++-
.../apache/juneau/marshall/MarshallingContext.java | 6 +-
.../apache/juneau/marshall/MarshallingSession.java | 33 ++++++--
.../marshall/MarshallingTraverseSession.java | 7 +-
.../juneau/marshall/httppart/HttpPartSchema.java | 12 ++-
.../apache/juneau/marshall/json5/Json5List.java | 5 +-
.../marshall/prototext/PrototextTokenizer.java | 3 +-
.../apache/juneau/marshall/toml/TomlTokenizer.java | 13 +--
.../apache/juneau/marshall/utils/Iso8601Utils.java | 18 ++--
.../apache/juneau/marshall/BeanRegistry_Test.java | 28 +++++--
.../org/apache/juneau/marshall/ClassMeta_Test.java | 11 +++
.../juneau/marshall/MarshallingContext_Test.java | 22 +++++
.../juneau/marshall/MarshallingSession_Test.java | 97 ++++++++++++++++++++++
.../httppart/HttpPartSchema_Validation_Test.java | 18 ++++
.../juneau/marshall/json5/Json5List_Test.java | 13 +++
.../prototext/PrototextTokenizer_Test.java | 18 ++++
.../juneau/marshall/toml/TomlTokenizer_Test.java | 18 ++++
.../juneau/marshall/utils/Iso8601Utils_Test.java | 8 ++
.../server/beans/ResourceDescription_Test.java | 19 +++++
.../juneau/rest/server/debug/DebugConfig_Test.java | 2 +
.../server/httppart/RequestAttributes_Test.java | 13 +++
.../httppart/RequestQueryParamList_Test.java | 14 ++++
.../client/classic/remote/RemoteOperationArg.java | 2 +-
.../juneau/http/header/ContentSecurityPolicy.java | 4 +-
.../http/remote/RrpcInterfaceMethodMeta.java | 8 +-
.../http/header/ContentSecurityPolicy_Test.java | 7 ++
.../http/remote/RrpcInterfaceMethodMeta_Test.java | 24 ++++--
.../juneau/rest/server/mcp/McpServerConfig.java | 4 +-
.../apache/juneau/rest/server/arg/CookieList.java | 10 ++-
.../assertions/FluentRequestHeaderAssertion.java | 2 +-
.../rest/server/beans/ResourceDescription.java | 6 +-
.../rest/server/debug/BasicDebugEnablement.java | 2 +-
.../juneau/rest/server/debug/DebugConfig.java | 2 +-
.../rest/server/httppart/RequestAttributes.java | 4 +
.../rest/server/httppart/RequestFormParamList.java | 2 +
.../rest/server/httppart/RequestPathParamList.java | 2 +
.../server/httppart/RequestQueryParamList.java | 2 +
.../rest/server/swagger/SwaggerException.java | 6 +-
.../juneau/rest/server/widget/MenuItemWidget.java | 6 +-
.../juneau/rest/server/arg/CookieList_Test.java | 48 +++++++++++
.../FluentRequestHeaderAssertion_Test.java | 62 ++++++++++++++
.../rest/server/swagger/SwaggerException_Test.java | 51 ++++++++++++
50 files changed, 730 insertions(+), 96 deletions(-)
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/AtomBuilder.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/AtomBuilder.java
index a9fc7f9825..8cfd29065b 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/AtomBuilder.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/AtomBuilder.java
@@ -33,11 +33,11 @@ import org.apache.juneau.marshall.*;
* Feed <jv>feed</jv> =
* <jsm>feed</jsm>(<js>"tag:juneau.sample.com,2013:1"</js>,
<js>"Juneau ATOM specification"</js>,
* <js>"2013-05-08T12:29:29Z"</js>)
- * .subtitle(<jsm>text</jsm>(<js>"html"</js>)
- * .children(<js>"A <em>lot</em> of effort
went into making this effortless"</js>))
- * .links(
+ * .setSubtitle(<jsm>text</jsm>(<js>"html"</js>)
+ * .setText(<js>"A <em>lot</em> of effort went
into making this effortless"</js>))
+ * .setLinks(
* <jsm>link</jsm>(<js>"alternate"</js>,
<js>"text/html"</js>, <js>"http://www.sample.com/"</js>)
- * .hreflang(<js>"en"</js>),
+ * .setHreflang(<js>"en"</js>),
* <jsm>link</jsm>(<js>"self"</js>,
<js>"application/atom+xml"</js>, <js>"http://www.sample.com/feed.atom"</js>)
* );
* </p>
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
index 9631b3e799..c8362ed3ab 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
@@ -737,7 +737,7 @@ public class JsonSchema {
@Swap(BooleanOrSchemaSwap.class)
public Object getAdditionalProperties() {
if (nn(additionalPropertiesBoolean))
- return additionalItemsBoolean;
+ return additionalPropertiesBoolean;
return additionalPropertiesSchema;
}
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
b/juneau-bean/juneau-bean-jsonschema/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
index d349e40a67..7c600b3f4f 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
@@ -477,6 +477,15 @@ public class JsonSchema_Test extends TestBase {
assertThrows(RuntimeException.class, () ->
x.setAdditionalProperties("invalid"));
}
+ @Test void b23e2_getAdditionalPropertiesAsBooleanFromGetter() {
+ // Regression: getAdditionalProperties() used to return the
additionalItems backing field, so a
+ // boolean additionalProperties could never be read back and
could be masked by additionalItems.
+ var x = new JsonSchema()
+ .setAdditionalItems(Boolean.FALSE)
+ .setAdditionalProperties(Boolean.TRUE);
+ assertEquals(Boolean.TRUE, x.getAdditionalProperties());
+ }
+
@Test void b23f_setItems_jsonSchemaArray_path() {
var x = new JsonSchema().setItems(new JsonSchemaArray(new
JsonSchemaRef("http://a")));
assertNotNull(x.getItemsAsSchemaArray());
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMeta.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMeta.java
index 6b33fbba58..bbc188e8dd 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMeta.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMeta.java
@@ -211,6 +211,26 @@ public class BeanMeta<T> {
* @return A {@link BeanMetaValue} containing the bean metadata (if
successful) or a reason why it's not a bean.
*/
public static <T> BeanMetaValue<T> create(BeanInfo<T> cm, ClassInfo
implClass) {
+ return create(cm, implClass, null);
+ }
+
+ /**
+ * Same as {@link #create(BeanInfo, ClassInfo)} but allows an explicit
{@link PropertyNamer} to override the one
+ * that would otherwise be derived from the bean filter or {@link
BeanConfigContext#getPropertyNamer()}.
+ *
+ * <p>
+ * The returned {@link BeanMeta} is <b>not</b> cached anywhere —
callers that need a namer-specific view (for
+ * example, {@code MarshallingSession#toBeanMap(Object,
PropertyNamer)}) build one on demand so the shared,
+ * {@code Class}-keyed default {@link BeanMeta} cache is never polluted
with alternate-namer property keys.
+ *
+ * @param <T> The class type.
+ * @param cm The bean type info for the class to create bean metadata
for. Must not be <jk>null</jk>.
+ * @param implClass Optional implementation class info to use when
looking for a no-arg constructor. Can be <jk>null</jk>.
+ * @param propertyNamer Optional property namer to use for deriving
property names. If <jk>null</jk>, the namer is
+ * resolved the normal way (bean filter, then {@link
BeanConfigContext#getPropertyNamer()}).
+ * @return A {@link BeanMetaValue} containing the bean metadata (if
successful) or a reason why it's not a bean.
+ */
+ public static <T> BeanMetaValue<T> create(BeanInfo<T> cm, ClassInfo
implClass, PropertyNamer propertyNamer) {
try {
var cfg = cm.getBeanConfigContext();
var ap = cfg.getAnnotationProvider();
@@ -229,7 +249,7 @@ public class BeanMeta<T> {
if ((!
cfg.getBeanClassVisibility().isVisible(cm.getModifiers()) ||
cm.isAnonymousClass()) && ! bmi.hasBeanRegistrationAnnotation(cfg, cm))
return notABean("Class is not public");
- var bm = new BeanMeta<>(cm, bmi.buildBeanFilter(cm),
null, implClass);
+ var bm = new BeanMeta<>(cm, bmi.buildBeanFilter(cm),
null, implClass, propertyNamer);
if (nn(bm.notABeanReason))
return notABean(bm.notABeanReason);
@@ -240,6 +260,21 @@ public class BeanMeta<T> {
}
}
+ /*
+ * Returns the property namer that should be used to derive property
names for this bean.
+ *
+ * <p>
+ * Precedence: an explicit per-instance {@link #propertyNamerOverride}
(used by the
+ * {@code toBeanMap(Object, PropertyNamer)} path) wins over everything;
otherwise the bean filter's namer, then the
+ * {@link BeanConfigContext#getPropertyNamer() configured default}.
Preserving this fallback order keeps the
+ * default-cache construction path behaviorally identical to before the
override was introduced.
+ */
+ private PropertyNamer effectivePropertyNamer() {
+ if (nn(propertyNamerOverride))
+ return propertyNamerOverride;
+ return o(beanFilter).map(x ->
x.getPropertyNamer()).orElse(config.getPropertyNamer());
+ }
+
/*
* Extracts the property name from {@link BeanProp @BeanProp} or {@link
Name @Name} annotations.
*
@@ -335,6 +370,7 @@ public class BeanMeta<T> {
private final Map<String,BeanPropertyMeta> properties;
// The properties on the target class.
private final Map<Method,String> setterProps;
// The setter properties on the target class.
private final boolean unsortedProperties;
// Whether properties should use natural JVM-dependent order.
+ private final PropertyNamer propertyNamerOverride;
// Optional per-instance namer override. Null means resolve the normal way
(bean filter, then config). Non-null takes precedence over both.
private final ClassInfo stopClass;
// The stop class for hierarchy traversal.
private final BeanPropertyMeta typeProperty;
// "_type" mock bean property.
private final String typePropertyName;
// "_type" property actual name.
@@ -400,7 +436,22 @@ public class BeanMeta<T> {
* @param implClass Optional implementation class constructor to use if
one cannot be found. Can be <jk>null</jk>.
*/
protected BeanMeta(BeanInfo<T> cm, BeanFilter bf, String[] pNames,
ClassInfo implClass) {
- this(cm, cm, cm.getBeanConfigContext(),
cm.getMarshallingContext(), bf, pNames, implClass);
+ this(cm, cm, cm.getBeanConfigContext(),
cm.getMarshallingContext(), bf, pNames, implClass, null);
+ }
+
+ /**
+ * Same as {@link #BeanMeta(BeanInfo, BeanFilter, String[], ClassInfo)}
but allows an explicit
+ * {@link PropertyNamer} override.
+ *
+ * @param cm The class metadata for the bean class. Must not be
<jk>null</jk>.
+ * @param bf Optional bean filter to apply. Can be <jk>null</jk>.
+ * @param pNames Explicit list of property names and order. If
<jk>null</jk>, properties are determined automatically.
+ * @param implClass Optional implementation class constructor to use if
one cannot be found. Can be <jk>null</jk>.
+ * @param propertyNamerOverride Optional namer that, when
non-<jk>null</jk>, takes precedence over the bean filter
+ * and {@link BeanConfigContext#getPropertyNamer()} for deriving
property names.
+ */
+ protected BeanMeta(BeanInfo<T> cm, BeanFilter bf, String[] pNames,
ClassInfo implClass, PropertyNamer propertyNamerOverride) {
+ this(cm, cm, cm.getBeanConfigContext(),
cm.getMarshallingContext(), bf, pNames, implClass, propertyNamerOverride);
}
/**
@@ -416,19 +467,20 @@ public class BeanMeta<T> {
* @param config The bean-modeling configuration. Must not be
<jk>null</jk>.
*/
protected BeanMeta(Class<T> beanClass, BeanConfigContext config) {
- this(null, info(assertArgNotNull("beanClass", beanClass)),
assertArgNotNull("config", config), null, null, null, null);
+ this(null, info(assertArgNotNull("beanClass", beanClass)),
assertArgNotNull("config", config), null, null, null, null, null);
}
@SuppressWarnings({
"java:S3776", // Cognitive complexity acceptable for bean
metadata initialization
- "java:S107" // 7 parameters needed to support both
construction paths
+ "java:S107" // 8 parameters needed to support both
construction paths plus the optional namer override
})
- private BeanMeta(BeanInfo<T> cm, ClassInfo ci0, BeanConfigContext
config, Object mc, BeanFilter bf, String[] pNames, ClassInfo implClass) {
+ private BeanMeta(BeanInfo<T> cm, ClassInfo ci0, BeanConfigContext
config, Object mc, BeanFilter bf, String[] pNames, ClassInfo implClass,
PropertyNamer propertyNamerOverride) {
classMeta = cm;
classInfo = ci0;
this.config = config;
marshallingContext = mc;
beanFilter = bf;
+ this.propertyNamerOverride = propertyNamerOverride;
implClassConstructor = o(implClass).map(x ->
x.getPublicConstructor(x2 -> x2.hasNumParameters(0)).orElse(null)).orElse(null);
fluentSetters = config.isFindFluentSetters() || (nn(bf) &&
bf.isFluentSetters());
stopClass = o(bf).map(x ->
x.getStopClass()).orElse(info(Object.class));
@@ -449,7 +501,7 @@ public class BeanMeta<T> {
var propertyBeanRegistriesTemp =
CollectionUtils.<BeanPropertyMeta,BeanRegistryLookup>map(); // Per-property
BeanRegistry side-map.
var unsortedPropertiesTemp = false;
var btList = ap.find(BeanType.class, classInfo);
- var propertyNamer = o(bf).map(x ->
x.getPropertyNamer()).orElse(config.getPropertyNamer());
+ var propertyNamer = effectivePropertyNamer();
// resolveTypePropertyName may return null on the commons-side
NOOP path; fall back to the configured default.
var resolvedTypePropertyName =
config.getBeanMetaInitializer().resolveTypePropertyName(config, classInfo);
@@ -1295,7 +1347,7 @@ public class BeanMeta<T> {
var ap = config.getAnnotationProvider();
var ci = classInfo;
var v = config.getBeanMethodVisibility();
- var pn = o(beanFilter).map(x ->
x.getPropertyNamer()).orElse(config.getPropertyNamer());
+ var pn = effectivePropertyNamer();
var suppressedFromBeanIgnoredFields =
findSuppressedPropertyNamesFromIgnoredFields(pn);
classHierarchy.get().forEach(c2 -> {
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
index a4dadb9fcb..d89cb20ba7 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
@@ -944,7 +944,7 @@ public class Config extends Context implements
ConfigEventListener {
*/
public Config removeImport(String sectionName, String importName) {
checkWrite();
- configMap.removeImport(sectionName, importName);
+ configMap.removeImport(section(sectionName), importName);
return this;
}
@@ -1120,7 +1120,7 @@ public class Config extends Context implements
ConfigEventListener {
*/
public Config setImport(String sectionName, String importName,
List<String> preLines) {
checkWrite();
- configMap.setImport(section(name), importName, preLines);
+ configMap.setImport(section(sectionName), importName, preLines);
return this;
}
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
index d9fdb3c4b9..cb6b9eda7f 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
@@ -550,20 +550,30 @@ public class ConfigMap implements ConfigStoreListener {
}
/**
- * Not implemented.
+ * Removes the import statement with the specified name.
+ *
+ * <p>
+ * The imported config is detached from this map: its entries are no
longer visible through this map and its
+ * change-propagation listeners are unregistered. No-op if no matching
import exists.
*
* @param section
- * The section name where to place the import statement.
+ * The section name where the import statement was placed.
* <br>Must not be <jk>null</jk>.
* <br>Use blank for the default section.
* @param importName
* The import name.
* <br>Must not be <jk>null</jk>.
* @return This object.
- * @throws UnsupportedOperationException Always - this operation is not
implemented.
*/
public ConfigMap removeImport(String section, String importName) {
- throw uoex();
+ checkSectionName(section);
+ try (var x = lock.write()) {
+ imports.stream().filter(y ->
y.getConfigName().equals(importName)).findFirst().ifPresent(y -> {
+ y.unregisterAll();
+ imports.remove(y);
+ });
+ }
+ return this;
}
/**
@@ -631,7 +641,12 @@ public class ConfigMap implements ConfigStoreListener {
}
/**
- * Not implemented.
+ * Creates the specified import statement if it doesn't already exist.
+ *
+ * <p>
+ * The named config is resolved through this map's {@link ConfigStore
store} and attached to this map so that its
+ * entries become visible through this map and subsequent changes to it
are propagated to this map's listeners.
+ * No-op if an import with the same name already exists.
*
* @param section
* The section name where to place the import statement.
@@ -644,10 +659,18 @@ public class ConfigMap implements ConfigStoreListener {
* Optional comment and blank lines to add immediately before the
import statement.
* <br>If <jk>null</jk>, previous pre-lines will not be replaced.
* @return This object.
- * @throws UnsupportedOperationException Always - this operation is not
implemented.
*/
public ConfigMap setImport(String section, String importName,
List<String> preLines) {
- throw uoex();
+ checkSectionName(section);
+ if (! isValidConfigName(importName))
+ throw iaex("Invalid import config name: '%s'",
importName);
+ try (var x = lock.write()) {
+ if (imports.stream().noneMatch(y ->
y.getConfigName().equals(importName)))
+ imports.add(new Import(store.getMap(importName,
format)).register(listeners));
+ } catch (IOException e) {
+ throw toRex(e);
+ }
+ return this;
}
/**
diff --git
a/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
b/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
index 53488d25bb..2137f5bd09 100644
---
a/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
+++
b/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
@@ -1882,12 +1882,26 @@ class Config_Test extends TestBase {
}
//====================================================================================================
- // Config.setImport(String, String, List) - throws
UnsupportedOperationException (not supported)
+ // Config.setImport(String, String, List) - attaches an imported config
so its entries resolve.
//====================================================================================================
- @Test void a53_setImport() {
- var c = init();
- assertThrows(UnsupportedOperationException.class, () ->
c.setImport("", "other.cfg", null));
+ @Test void a53_setImport() throws Exception {
+ var ms = MemoryStore.create().build();
+ ms.write("A53a", "", "importedKey=1");
+ ms.write("A53b", "", "");
+ var c = Config.create("A53b").store(ms).build();
+ assertNull(c.get("importedKey").orElse(null));
+
+ c.setImport("", "A53a", null);
+ assertEquals("1", c.get("importedKey").get());
+
+ // setImport is idempotent for an already-registered import.
+ c.setImport("", "A53a", null);
+ assertEquals("1", c.get("importedKey").get());
+
+ // Read-only configs still reject the write.
+ var ro = Config.create("A53b").store(ms).readOnly().build();
+ assertThrows(UnsupportedOperationException.class, () ->
ro.setImport("", "A53a", null));
}
//====================================================================================================
@@ -1943,8 +1957,21 @@ class Config_Test extends TestBase {
}
@Test void a61_removeImport() throws Exception {
- var c = init();
- assertThrows(UnsupportedOperationException.class, () ->
c.removeImport("", "nonexistent.cfg"));
+ var ms = MemoryStore.create().build();
+ ms.write("A61a", "", "importedKey=1");
+ ms.write("A61b", "", "<A61a>");
+ var c = Config.create("A61b").store(ms).build();
+ assertEquals("1", c.get("importedKey").get());
+
+ c.removeImport("", "A61a");
+ assertNull(c.get("importedKey").orElse(null));
+
+ // Removing a non-existent import is a no-op (does not throw).
+ c.removeImport("", "nonexistent");
+
+ // Read-only configs still reject the write.
+ var ro = Config.create("A61b").store(ms).readOnly().build();
+ assertThrows(UnsupportedOperationException.class, () ->
ro.removeImport("", "A61a"));
}
@Test void a62_set_nullValueNonExistentKey() throws Exception {
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/BeanRegistry.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/BeanRegistry.java
index 856c312352..f482dd79cc 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/BeanRegistry.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/BeanRegistry.java
@@ -110,11 +110,11 @@ public class BeanRegistry implements BeanRegistryLookup {
/**
* Given the specified class, return the dictionary name for it.
*
- * @param c The class to lookup in this registry.
+ * @param c The class to lookup in this registry. Can be <jk>null</jk>.
* @return The dictionary name for the specified class in this
registry, or <jk>null</jk> if not found.
*/
public String getTypeName(ClassMeta<?> c) {
- return isEmpty ? null : reverseMap.get(c.inner());
+ return isEmpty || c == null ? null : reverseMap.get(c.inner());
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ClassMeta.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ClassMeta.java
index 1a40eb8fa9..a80999c6da 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ClassMeta.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ClassMeta.java
@@ -498,6 +498,27 @@ public final class ClassMeta<T> extends BeanInfo<T> {
return beanMeta.get().beanMeta();
}
+ /**
+ * Same as {@link #getBeanMeta()} but derives property names using the
supplied {@link PropertyNamer} instead of
+ * the one configured on the owning context.
+ *
+ * <p>
+ * Unlike {@link #getBeanMeta()}, the returned {@link BeanMeta} is
built fresh on each call and is <b>not</b>
+ * cached — the shared, {@code Class}-keyed default {@link BeanMeta}
(which uses the context's default namer) is
+ * left untouched. This keeps the cache safe when callers request an
alternate property-naming strategy (e.g.
+ * {@code MarshallingSession#toBeanMap(Object, PropertyNamer)}).
+ *
+ * @param propertyNamer The property namer to use for deriving property
names. Must not be <jk>null</jk>.
+ * @return
+ * A newly-built {@link BeanMeta} whose property names reflect the
supplied namer, or <jk>null</jk> if there is
+ * no bean meta associated with this class.
+ */
+ public BeanMeta<T> getBeanMeta(PropertyNamer propertyNamer) {
+ if (! cat.isUnknown())
+ return null;
+ return BeanMeta.create(this, implClass.get(),
propertyNamer).beanMeta();
+ }
+
/**
* Returns the bean registry for this class.
*
@@ -1420,10 +1441,12 @@ public final class ClassMeta<T> extends BeanInfo<T> {
* Similar to {@link #equals(Object)} except primitive and Object types
that are similar are considered the same.
* (e.g. <jk>boolean</jk> == <c>Boolean</c>).
*
- * @param cm The class meta to compare to.
+ * @param cm The class meta to compare to. Can be <jk>null</jk>.
* @return <jk>true</jk> if the specified class-meta is equivalent to
this one.
*/
public boolean same(ClassMeta<?> cm) {
+ if (cm == null)
+ return false;
if (equals(cm))
return true;
return (isPrimitive() && cat.same(cm.cat));
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
index dbd8fc0368..56e6a72294 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
@@ -3336,7 +3336,11 @@ public class MarshallingContext extends Context
implements ConversionFinder, Bea
* @return This object.
*/
public <T,S> Builder swap(Class<T> normalClass, Class<S>
swappedClass, ThrowingFunction<T,S> swapFunction) {
- return swap(normalClass, swappedClass, swapFunction,
null);
+ assertArgNotNull(ARG_normalClass, normalClass);
+ assertArgNotNull(ARG_swappedClass, swappedClass);
+ assertArgNotNull(ARG_swapFunction, swapFunction);
+ swaps().add(0, new FunctionalSwap<>(normalClass,
swappedClass, swapFunction, null));
+ return this;
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
index b9ee19016d..5470a87384 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
@@ -1219,16 +1219,39 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
* BeanMap<Person> <jv>beanMap</jv> =
MarshallingContext.<jsf>DEFAULT</jsf>.toBeanMap(<jk>new</jk> Person(),
PropertyNamerDLC.<jsf>INSTANCE</jsf>);
* </p>
*
+ * <p>
+ * The supplied {@code propertyNamer} controls the names of the
properties exposed by the returned
+ * {@link BeanMap}. For example, passing {@link
PropertyNamerDLC#INSTANCE} renames a <c>fooBar</c> property to
+ * <c>foo-bar</c>.
+ *
+ * <p>
+ * When {@code propertyNamer} is <jk>null</jk> or equal to this
session's {@link #getPropertyNamer() default
+ * namer}, the shared, {@code Class}-keyed {@link BeanMeta} cache is
used exactly as by {@link #toBeanMap(Object)}.
+ * Otherwise a fresh, non-cached {@link BeanMeta} is built with the
supplied namer so the default cache is never
+ * polluted with alternate-namer property keys.
+ *
* @param <T> The class of the object being wrapped.
* @param o The object to wrap in a map interface. Must not be null.
- * @param propertyNamer The property namer to use.
+ * @param propertyNamer The property namer to use. Can be
<jk>null</jk> to use the session's default namer.
* @return The wrapped object.
*/
- @SuppressWarnings({
- "java:S1172" // Parameter reserved for future property naming
strategy support
- })
public final <T> BeanMap<T> toBeanMap(T o, PropertyNamer propertyNamer)
{
- return this.toBeanMap(o, (Class<T>)o.getClass());
+ assertArgNotNull(ARG_o, o);
+ if (o instanceof BeanMap o2)
+ return o2;
+
+ // Preserve the cached default-namer path when no override (or
the override matches the default) is supplied.
+ if (propertyNamer == null ||
propertyNamer.equals(getPropertyNamer()))
+ return this.toBeanMap(o, (Class<T>)o.getClass());
+
+ var c = (Class<T>)o.getClass();
+ var cm = getClassMeta(c);
+ var m = cm.getBeanMeta(propertyNamer);
+ if (m == null)
+ throw brex(c, "Class is not a bean. Reason='%s'",
cm.getNotABeanReason());
+ var bm = new BeanMap<>(o, m);
+ bm.setBeanSession(this);
+ return bm;
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingTraverseSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingTraverseSession.java
index 4262634fcb..03a23cb5e0 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingTraverseSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingTraverseSession.java
@@ -19,6 +19,7 @@ package org.apache.juneau.marshall;
import static java.util.Collections.*;
import static org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.Shorts.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import java.util.*;
import java.util.function.*;
@@ -306,7 +307,11 @@ public class MarshallingTraverseSession extends
MarshallingSession {
* @param args Optional {@link String#format(String, Object...)
String.format}-style arguments.
*/
protected void onError(Throwable t, String msg, Object...args) {
- super.addWarning(msg, args);
+ // Honor the throwable per the @param contract: fold its
localized message into the recorded warning when present.
+ if (t == null)
+ super.addWarning(msg, args);
+ else
+ super.addWarning("%s%n\tCaused by: %s", f(msg, args),
localizedMessage(t));
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/HttpPartSchema.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/HttpPartSchema.java
index 191566e7aa..d49389beb6 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/HttpPartSchema.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/HttpPartSchema.java
@@ -653,7 +653,9 @@ public class HttpPartSchema {
* @return This object.
*/
public Builder collectionFormat(HttpPartCollectionFormat value)
{
- collectionFormat = value;
+ // Guard null for parity with the String overload
(which ine()-guards); a null must not overwrite the field.
+ if (value != null)
+ collectionFormat = value;
return this;
}
@@ -1176,7 +1178,9 @@ public class HttpPartSchema {
* @return This object.
*/
public Builder format(HttpPartFormat value) {
- format = value;
+ // Guard null for parity with the String overload
(which ine()-guards); a null must not overwrite the field.
+ if (value != null)
+ format = value;
return this;
}
@@ -2393,7 +2397,9 @@ public class HttpPartSchema {
* @return This object.
*/
public Builder type(HttpPartDataType value) {
- type = value;
+ // Guard null for parity with the String overload
(which ine()-guards); a null must not overwrite the field.
+ if (value != null)
+ type = value;
return this;
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
index 5d2edf2a8f..1409edc98b 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json5/Json5List.java
@@ -253,11 +253,8 @@ public class Json5List extends MarshalledList {
* @return A new list (empty if the input was <jk>null</jk>), never
<jk>null</jk>.
* @throws ParseException Malformed input encountered.
*/
- @SuppressWarnings({
- "java:S1172" // Parameter reserved for future parser context
support
- })
public static Json5List ofString(Reader in, Parser p) throws
ParseException {
- return in == null ? new Json5List() : new Json5List(in);
+ return in == null ? new Json5List() : new Json5List(in, p);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/prototext/PrototextTokenizer.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/prototext/PrototextTokenizer.java
index 74573636cd..539f5b6ac0 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/prototext/PrototextTokenizer.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/prototext/PrototextTokenizer.java
@@ -545,7 +545,8 @@ class PrototextTokenizer {
}
ParseException parseException(String message) {
- return new ParseException(message + " at line " + line + ",
column " + column);
+ // Pass the raw message as a %s arg so any literal '%' in
echoed input can't be interpreted as a printf directive.
+ return new ParseException("%s at line %s, column %s", message,
line, column);
}
private static boolean isLetterOrUnderscore(int c) {
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/toml/TomlTokenizer.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/toml/TomlTokenizer.java
index 6774be5ff4..b1dc331d20 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/toml/TomlTokenizer.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/toml/TomlTokenizer.java
@@ -149,7 +149,8 @@ class TomlTokenizer {
}
ParseException parseException(String message) {
- return new ParseException(message + " at line " + line + ",
column " + column);
+ // Pass the raw message as a %s arg so any literal '%' in
echoed input can't be interpreted as a printf directive.
+ return new ParseException("%s at line %s, column %s", message,
line, column);
}
String readBareKey() throws IOException, ParseException {
@@ -557,30 +558,30 @@ class TomlTokenizer {
try {
return
java.time.OffsetDateTime.parse(s.replace(" ", "T"));
} catch (Exception e) {
- throw new ParseException("Invalid offset
date-time: " + s, e);
+ throw new ParseException(e, "Invalid offset
date-time: %s", s);
}
}
if (s.contains("T") || s.contains(" ")) {
try {
return
java.time.LocalDateTime.parse(s.replace(" ", "T"));
} catch (Exception e) {
- throw new ParseException("Invalid local
date-time: " + s, e);
+ throw new ParseException(e, "Invalid local
date-time: %s", s);
}
}
if (s.contains("-") && s.length() == 10) {
try {
return java.time.LocalDate.parse(s);
} catch (Exception e) {
- throw new ParseException("Invalid local date: "
+ s, e);
+ throw new ParseException(e, "Invalid local
date: %s", s);
}
}
if (s.contains(":")) {
try {
return java.time.LocalTime.parse(s);
} catch (Exception e) {
- throw new ParseException("Invalid local time: "
+ s, e);
+ throw new ParseException(e, "Invalid local
time: %s", s);
}
}
- throw new ParseException("Invalid date-time: " + s);
+ throw new ParseException("Invalid date-time: %s", s);
}
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/utils/Iso8601Utils.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/utils/Iso8601Utils.java
index 9332004999..05419af957 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/utils/Iso8601Utils.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/utils/Iso8601Utils.java
@@ -87,15 +87,17 @@ public final class Iso8601Utils {
* {@link #formatDate}, {@link #formatCalendar}, {@link
#formatTemporal},
* {@link #formatDuration}, {@link #formatPeriod}.
*
- * @param value The value to format. Must not be <jk>null</jk> (unlike
the per-type helpers, this dispatcher does not null-guard).
+ * @param value The value to format. <jk>null</jk> returns
<jk>null</jk> (consistent with the per-type helpers).
* @param type The class metadata for the value (used for selecting the
appropriate formatter for Temporal types).
* @param timeZone The session time zone (used when the value lacks
zone info).
- * @return The ISO 8601 string representation.
+ * @return The ISO 8601 string representation, or <jk>null</jk> if
{@code value} is <jk>null</jk>.
*/
@SuppressWarnings({
"java:S1172" // type kept for API compatibility; future callers
may use it for per-type dispatch hints
})
public static String format(Object value, ClassMeta<?> type, TimeZone
timeZone) {
+ if (value == null)
+ return null;
if (value instanceof Duration d)
return formatDuration(d,
DurationFormat.ISO_8601_WITH_DAYS);
if (value instanceof Period p)
@@ -208,15 +210,17 @@ public final class Iso8601Utils {
/**
* Formats a date/time value as an ISO date (date-only, for OpenAPI
'date' format).
*
- * @param value The value to format. Must not be <jk>null</jk>.
+ * @param value The value to format. <jk>null</jk> returns
<jk>null</jk>.
* @param type The class metadata.
* @param timeZone The session time zone.
- * @return The ISO date string.
+ * @return The ISO date string, or <jk>null</jk> if {@code value} is
<jk>null</jk>.
*/
@SuppressWarnings({
"java:S1172" // type kept for API compatibility; callers pass
ClassMeta context for potential future use
})
public static String formatAsDate(Object value, ClassMeta<?> type,
TimeZone timeZone) {
+ if (value == null)
+ return null;
ZoneId zoneId = timeZone != null ? timeZone.toZoneId() :
ZoneId.systemDefault();
if (value instanceof Calendar c) {
ZonedDateTime zdt = (c instanceof GregorianCalendar gc)
? gc.toZonedDateTime() : c.toInstant().atZone(c.getTimeZone().toZoneId());
@@ -232,15 +236,17 @@ public final class Iso8601Utils {
/**
* Formats a date/time value as an ISO date-time (for OpenAPI
'date-time' format).
*
- * @param value The value to format. Must not be <jk>null</jk>.
+ * @param value The value to format. <jk>null</jk> returns
<jk>null</jk>.
* @param type The class metadata.
* @param timeZone The session time zone.
- * @return The ISO date-time string.
+ * @return The ISO date-time string, or <jk>null</jk> if {@code value}
is <jk>null</jk>.
*/
@SuppressWarnings({
"java:S1172" // type kept for API compatibility; callers pass
ClassMeta context for potential future use
})
public static String formatAsDateTime(Object value, ClassMeta<?> type,
TimeZone timeZone) {
+ if (value == null)
+ return null;
ZoneId zoneId = timeZone != null ? timeZone.toZoneId() :
ZoneId.systemDefault();
if (value instanceof Calendar c) {
ZonedDateTime zdt = (c instanceof GregorianCalendar gc)
? gc.toZonedDateTime() : c.toInstant().atZone(c.getTimeZone().toZoneId());
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/BeanRegistry_Test.java
similarity index 55%
copy from
juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
copy to
juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/BeanRegistry_Test.java
index 854e75ea81..9a11a01c44 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/BeanRegistry_Test.java
@@ -14,19 +14,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.juneau.rest.server.beans;
+package org.apache.juneau.marshall;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.juneau.*;
-import org.apache.juneau.marshall.marshaller.*;
import org.junit.jupiter.api.*;
-class ResourceDescription_Test extends TestBase {
+/**
+ * Validates {@link BeanRegistry}.
+ */
+class BeanRegistry_Test extends TestBase {
+
+ @Marshalled(typeName="A")
+ public static class A {
+ public String f;
+ }
+
+ // Regression: getTypeName(ClassMeta) NPE'd on a null argument for a
non-empty registry (it dereferenced
+ // c.inner()), even though its sibling getTypeName(Class) guarded null
and both share the "or null" @return.
+ @Test void getTypeName_nullClassMeta() {
+ var bc =
MarshallingContext.create().beanDictionary(A.class).build();
+ var reg = bc.getBeanRegistry();
- @Test void a01_basic() throws Exception {
- var rd = new ResourceDescription("a","b?c=d&e=f","g");
- assertEquals("<table><tr><td>name</td><td><a
href=\"/b?c=d&e=f\">a</a></td></tr><tr><td>description</td><td>g</td></tr></table>",
Html.of(rd));
- assertEquals("{name:'a',description:'g'}", Json5.of(rd));
+ assertEquals("A", reg.getTypeName(bc.getClassMeta(A.class)));
+ assertNull(reg.getTypeName((ClassMeta<?>)null));
+ assertNull(reg.getTypeName((Class<?>)null));
}
-}
\ No newline at end of file
+}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ClassMeta_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ClassMeta_Test.java
index 7edbb05a1a..e8482bd813 100755
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ClassMeta_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ClassMeta_Test.java
@@ -320,4 +320,15 @@ class ClassMeta_Test extends TestBase {
c3.forEachAnnotation(A.class, x -> x.value() == 5, x ->
l4.add(x.value()));
assertList(l4, "5");
}
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // same(null)
+ // Regression: same(null) returned false for non-primitive types but
NPE'd for primitive types
+ // (the primitive branch dereferenced the null arg via
cat.same(cm.cat)). Both must return false now.
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Test void same_nullArg() {
+ assertFalse(bc.getClassMeta(String.class).same(null));
+ assertFalse(bc.getClassMeta(int.class).same(null));
+ }
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/MarshallingContext_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/MarshallingContext_Test.java
index 00f6c22410..b9783ac690 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/MarshallingContext_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/MarshallingContext_Test.java
@@ -713,4 +713,26 @@ class BeanContext_Test extends TestBase {
var cm = bc.getClassMeta(List.class, String.class);
assertNotNull(cm);
}
+
+
//====================================================================================================
+ // Functional swap(...) builder overloads.
+ // Regression: the 3-arg serialize-only swap(nc,sc,swapFunction)
overload used to delegate through
+ // the 4-arg overload's assertArgNotNull(unswapFunction) and therefore
threw on EVERY call, so a
+ // serialize-only swap could never be registered.
+
//====================================================================================================
+
+ public static class PSwapBean {
+ public String value;
+ public PSwapBean(String value) { this.value = value; }
+ }
+
+ @Test void p01_functionalSwap_serializeOnly() throws Exception {
+ var s = Json5Serializer.create().swap(PSwapBean.class,
String.class, x -> x.value).build();
+ assertEquals("'foo'", s.write(new PSwapBean("foo")));
+ }
+
+ @Test void p02_functionalSwap_twoWay() throws Exception {
+ var s = Json5Serializer.create().swap(PSwapBean.class,
String.class, x -> x.value, PSwapBean::new).build();
+ assertEquals("'foo'", s.write(new PSwapBean("foo")));
+ }
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/MarshallingSession_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/MarshallingSession_Test.java
new file mode 100644
index 0000000000..5033cb1f81
--- /dev/null
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/MarshallingSession_Test.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.marshall;
+
+import static org.apache.juneau.test.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.bean.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link MarshallingSession#toBeanMap(Object, PropertyNamer)}.
+ */
+class MarshallingSession_Test extends TestBase {
+
+ MarshallingContext bc = MarshallingContext.DEFAULT;
+ MarshallingSession bs = MarshallingContext.DEFAULT_SESSION;
+
+ public static class A {
+ public String fooBar = "x";
+ public String qux = "y";
+ }
+
+
//====================================================================================================
+ // toBeanMap(Object, PropertyNamer) honors the supplied namer for
property names.
+
//====================================================================================================
+
+ @Test void a01_dashedLowerCaseNamer() {
+ var m = bs.toBeanMap(new A(), PropertyNamerDLC.INSTANCE);
+ assertList(m.keySet(), "foo-bar", "qux");
+ assertEquals("x", m.get("foo-bar"));
+ assertEquals("y", m.get("qux"));
+ }
+
+ @Test void a02_dashedUpperCaseStartNamer() {
+ var m = bs.toBeanMap(new A(), PropertyNamerDUCS.INSTANCE);
+ assertList(m.keySet(), "Foo-Bar", "Qux");
+ assertEquals("x", m.get("Foo-Bar"));
+ assertEquals("y", m.get("Qux"));
+ }
+
+ public static class A03_UpperNamer implements PropertyNamer {
+ @Override /* Overridden from PropertyNamer */
+ public String getPropertyName(String name) {
+ return name == null ? null : name.toUpperCase();
+ }
+ }
+
+ @Test void a03_customAllCapsNamer() {
+ var m = bs.toBeanMap(new A(), new A03_UpperNamer());
+ assertList(m.keySet(), "FOOBAR", "QUX");
+ assertEquals("x", m.get("FOOBAR"));
+ }
+
+
//====================================================================================================
+ // The null / default-namer path is unchanged (uses the shared,
Class-keyed BeanMeta cache).
+
//====================================================================================================
+
+ @Test void b01_nullNamerMatchesDefault() {
+ var m = bs.toBeanMap(new A(), (PropertyNamer)null);
+ var expected = bs.toBeanMap(new A());
+ assertList(m.keySet(), "fooBar", "qux");
+ assertEquals(expected.keySet(), m.keySet());
+ }
+
+ @Test void b02_defaultNamerUsesCachedBeanMeta() {
+ // Passing the session's own default namer must reuse the
cached BeanMeta (same instance as no-namer path).
+ var m = bs.toBeanMap(new A(), bs.getPropertyNamer());
+ var cached = bc.getClassMeta(A.class).getBeanMeta();
+ assertSame(cached, m.getMeta());
+ assertList(m.keySet(), "fooBar", "qux");
+ }
+
+ @Test void b03_overrideNamerDoesNotPolluteCache() {
+ // Building an alternate-namer BeanMap must not change the
shared cached BeanMeta's property names.
+ var before = bc.getClassMeta(A.class).getBeanMeta();
+ bs.toBeanMap(new A(), PropertyNamerDLC.INSTANCE);
+ var after = bc.getClassMeta(A.class).getBeanMeta();
+ assertSame(before, after);
+ assertList(after.getProperties().keySet(), "fooBar", "qux");
+ }
+}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/httppart/HttpPartSchema_Validation_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/httppart/HttpPartSchema_Validation_Test.java
index d035174179..79dd61ddde 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/httppart/HttpPartSchema_Validation_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/httppart/HttpPartSchema_Validation_Test.java
@@ -1422,4 +1422,22 @@ class HttpPartSchema_Validation_Test extends TestBase {
}
assertTrue(levels >= depth, "Expected the nested schema to be
reachable to full depth");
}
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Enum-setter null parity
+ // Regression:
type(HttpPartDataType)/format(HttpPartFormat)/collectionFormat(HttpPartCollectionFormat)
had no
+ // null guard (unlike their ine()-guarded String overloads), so with
noValidate() an explicit null overwrote the
+ // non-null default and the getter returned null. A null enum must now
be ignored (default preserved).
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Test void u01_enumSetters_nullIgnored() {
+ var s = HttpPartSchema.create().noValidate()
+ .type((HttpPartDataType)null)
+ .format((HttpPartFormat)null)
+ .collectionFormat((HttpPartCollectionFormat)null)
+ .build();
+ assertEquals(HttpPartDataType.NO_TYPE, s.getType());
+ assertEquals(HttpPartFormat.NO_FORMAT, s.getFormat());
+ assertEquals(HttpPartCollectionFormat.NO_COLLECTION_FORMAT,
s.getCollectionFormat());
+ }
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
index 630fc20c8d..9f2cfa6e14 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/json5/Json5List_Test.java
@@ -23,6 +23,8 @@ import java.io.*;
import org.apache.juneau.*;
import org.apache.juneau.marshall.collections.*;
+import org.apache.juneau.marshall.json.*;
+import org.apache.juneau.marshall.parser.*;
import org.junit.jupiter.api.*;
/**
@@ -124,6 +126,17 @@ class Json5List_Test extends TestBase {
assertEquals(3, l.size());
}
+ @Test void a07b_ofStringReaderHonorsParser() throws Exception {
+ // Regression: ofString(Reader, Parser) ignored its Parser p
and always used Json5Parser.DEFAULT.
+ // Json5Parser allows JS comments; the strict JsonParser
rejects them ("Javascript comment detected"),
+ // so honoring p makes the strict parser throw where the
ignored-parser path silently succeeded.
+ var json5WithComment = "[1,/*c*/2,3]";
+ assertEquals(3, Json5List.ofString(new
StringReader(json5WithComment), Json5Parser.DEFAULT).size());
+ assertThrows(ParseException.class, () -> Json5List.ofString(new
StringReader(json5WithComment), JsonParser.DEFAULT));
+ // The CharSequence overload already honored p — the Reader
overload now matches it.
+ assertThrows(ParseException.class, () ->
Json5List.ofString(json5WithComment, JsonParser.DEFAULT));
+ }
+
@Test void a07_ctorReaderUsesJson5Parser() throws Exception {
var l = new Json5List(new StringReader("[1,2,3]"));
assertEquals(3, l.size());
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextTokenizer_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextTokenizer_Test.java
index 95744c9327..e43f17f67b 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextTokenizer_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextTokenizer_Test.java
@@ -613,4 +613,22 @@ class PrototextTokenizer_Test {
assertThrows(ParseException.class, () ->
PrototextParser.DEFAULT.read("n:
0777777777777777777777777", JsonMap.class));
}
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // parseException printf-injection safety.
+ // A literal '%' echoed from an offending token must survive verbatim
and must not be interpreted as a
+ // printf directive (which would previously mangle the message or
throw).
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Test void c01_parseExceptionLiteralPercentNotFormatted() {
+ var t = tok("");
+ var ex = t.parseException("Unexpected character: %");
+ assertTrue(ex.getMessage().startsWith("Unexpected character: %
at line "));
+ }
+
+ @Test void c02_parseExceptionUnexpectedCharPercent() {
+ // Drives an actual parse error whose echoed character is a
literal '%'.
+ var ex = assertThrows(ParseException.class, () ->
tok("%").peek());
+ assertTrue(ex.getMessage().contains("Unexpected character: %"));
+ }
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/toml/TomlTokenizer_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/toml/TomlTokenizer_Test.java
index 43a9f821a9..8593e450d7 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/toml/TomlTokenizer_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/toml/TomlTokenizer_Test.java
@@ -873,4 +873,22 @@ class TomlTokenizer_Test extends TestBase {
assertTrue(ex.getMessage().contains("line"));
assertTrue(ex.getMessage().contains("column"));
}
+
+ @Test
+ void p02_parseExceptionLiteralPercentNotFormatted() {
+ // A literal '%' in the message must survive verbatim (no
printf re-interpretation).
+ var tok = t("");
+ var ex = tok.parseException("bad token: 100%");
+ assertTrue(ex.getMessage().startsWith("bad token: 100% at line
"));
+ }
+
+ @Test
+ void p03_parseDateTimeStringPercentPreservesMessageAndCause() {
+ // Previously new ParseException("Invalid offset date-time: " +
s, e) bound to (String, Object...):
+ // the cause 'e' was dropped and a stray '%' in the scanned
value was re-interpreted as a printf
+ // directive (throwing a formatting exception). The fix passes
'e' as the cause and 's' as a %s arg.
+ var ex = assertThrows(ParseException.class, () ->
TomlTokenizer.parseDateTimeString("1979-05-27T07:32:00%Z"));
+ assertTrue(ex.getMessage().contains("1979-05-27T07:32:00%Z"));
+ assertNotNull(ex.getCause());
+ }
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/utils/Iso8601Utils_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/utils/Iso8601Utils_Test.java
index 033a19d081..2f81f44580 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/utils/Iso8601Utils_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/utils/Iso8601Utils_Test.java
@@ -83,6 +83,14 @@ class Iso8601Utils_Test extends TestBase {
assertEquals("some-string", r);
}
+ @Test void a07_format_nullValue() {
+ // Regression: the three dispatchers deref'd value unguarded
(NPE on null), unlike the per-type helpers
+ // that all return null. Aligned per Q4 — null value now
returns null.
+ assertNull(Iso8601Utils.format(null,
BC.getClassMeta(String.class), UTC));
+ assertNull(Iso8601Utils.formatAsDate(null,
BC.getClassMeta(Date.class), UTC));
+ assertNull(Iso8601Utils.formatAsDateTime(null,
BC.getClassMeta(Date.class), UTC));
+ }
+
//------------------------------------------------------------------------------------------------------------------
// formatDuration — null format defaults to ISO_8601_WITH_DAYS (line
122), null value (line 120)
//------------------------------------------------------------------------------------------------------------------
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
index 854e75ea81..18f1f9eab3 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
@@ -29,4 +29,23 @@ class ResourceDescription_Test extends TestBase {
assertEquals("<table><tr><td>name</td><td><a
href=\"/b?c=d&e=f\">a</a></td></tr><tr><td>description</td><td>g</td></tr></table>",
Html.of(rd));
assertEquals("{name:'a',description:'g'}", Json5.of(rd));
}
+
+ // Regression: hashCode()/compareTo() dereferenced getName() unguarded,
so an unpopulated ResourceDescription
+ // (public no-arg ctor leaves 'name' null) NPE'd when hashed, sorted,
or added to a TreeSet — inconsistent with
+ // the already-null-safe equals().
+ @Test void a02_nullNameIsHashableAndComparable() {
+ var empty = new ResourceDescription();
+ assertEquals(0, empty.hashCode());
+ assertEquals(0, empty.compareTo(new ResourceDescription()));
+
+ var named = new ResourceDescription().name("a");
+ assertTrue(empty.compareTo(named) < 0);
+ assertTrue(named.compareTo(empty) > 0);
+
+ // Can be used as a TreeSet element without NPE.
+ var set = new java.util.TreeSet<ResourceDescription>();
+ set.add(empty);
+ set.add(named);
+ assertEquals(2, set.size());
+ }
}
\ No newline at end of file
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/debug/DebugConfig_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/debug/DebugConfig_Test.java
index 9385dad092..d93ec2cf7d 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/debug/DebugConfig_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/debug/DebugConfig_Test.java
@@ -190,6 +190,8 @@ class DebugConfig_Test extends TestBase {
assertNotNull(rule, "rule must be retrievable via getRuleFor");
assertEquals(Level.FINE, rule.getLevel());
assertNull(cfg.getRuleFor("com.example.Bar"), "no rule should
be returned for unconfigured target");
+ // Regression: getRuleFor(null) NPE'd on the null-hostile
Map.copyOf(...) map despite the "or null" @return.
+ assertNull(cfg.getRuleFor(null), "null target must return null,
not NPE");
}
@Test void b09_chainedBuilder_allFluent() {
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestAttributes_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestAttributes_Test.java
index cb3fe54ae5..f657e67959 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestAttributes_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestAttributes_Test.java
@@ -158,6 +158,15 @@ class RequestAttributes_Test extends TestBase {
var s = attrs.toString();
return "len=" + (!s.isEmpty()) + ",hasTs=" +
s.contains("ts");
}
+
+ // Regression: addDefault(null) NPE'd for all three "Can be
null" overloads.
+ @RestGet(path="/addDefaultNullIsNoOp")
+ public String addDefaultNullIsNoOp(RequestAttributes attrs) {
+ attrs.addDefault((NamedAttribute[])null);
+ attrs.addDefault((java.util.List<NamedAttribute>)null);
+ attrs.addDefault((NamedAttributeMap)null);
+ return "ok";
+ }
}
//------------------------------------------------------------------------------------------------------------------
@@ -213,4 +222,8 @@ class RequestAttributes_Test extends TestBase {
@Test void a11_toString_isInformative() throws Exception {
client().get("/toString").run().assertContent("len=true,hasTs=true");
}
+
+ @Test void a12_addDefault_nullIsNoOp() throws Exception {
+ client().get("/addDefaultNullIsNoOp").run().assertContent("ok");
+ }
}
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList_Test.java
index fc7ee6096f..bfe4365888 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList_Test.java
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList_Test.java
@@ -451,6 +451,14 @@ class RequestQueryParamList_Test extends TestBase {
return "iae:" + e.getMessage().contains("name");
}
}
+ // Regression: addDefault(null) NPE'd — "Can be null" pairs
must be tolerated as an empty default set.
+ @RestGet(path="/addDefaultNullIsNoOp")
+ public String addDefaultNullIsNoOp(RequestQueryParamList q) {
+ var before = q.size();
+ q.addDefault((HttpPart[])null);
+ q.addDefault((java.util.List<? extends HttpPart>)null);
+ return "size=" + before + "->" + q.size();
+ }
}
@Test
@@ -465,4 +473,10 @@ class RequestQueryParamList_Test extends TestBase {
c.get("/getFirstNullThrows").run().assertContent("iae:true");
c.get("/getLastNullThrows").run().assertContent("iae:true");
}
+
+ @Test
+ void b02_addDefaultNullIsNoOp() throws Exception {
+ var c = MockRestClient.build(B.class);
+
c.get("/addDefaultNullIsNoOp?x=1").run().assertContent("size=1->1");
+ }
}
diff --git
a/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/remote/RemoteOperationArg.java
b/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/remote/RemoteOperationArg.java
index 080fa49c2a..55be960a11 100644
---
a/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/remote/RemoteOperationArg.java
+++
b/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/remote/RemoteOperationArg.java
@@ -117,7 +117,7 @@ public class RemoteOperationArg {
/**
* Returns the HTTP part serializer to use for serializing this part.
*
- * @return The HTTP part serializer, or the default if not specified.
+ * @return The HTTP part serializer, or an empty {@link Optional} if
not specified.
*/
public Optional<HttpPartSerializer> getSerializer() { return
serializer; }
diff --git
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/header/ContentSecurityPolicy.java
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/header/ContentSecurityPolicy.java
index 0054df2a3e..494c45f86c 100644
---
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/header/ContentSecurityPolicy.java
+++
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/header/ContentSecurityPolicy.java
@@ -491,7 +491,7 @@ public class ContentSecurityPolicy extends HttpStringHeader
{
* Replaces any previously-registered source list for the same
directive while preserving its original position.
*
* @param name The directive name (e.g. <js>"worker-src"</js>).
Must not be <jk>null</jk> or blank.
- * @param sources The source expressions. May be empty (renders
the bare directive name).
+ * @param sources The source expressions. May be empty or
<jk>null</jk> (renders the bare directive name).
* @return This object.
* @throws IllegalArgumentException If {@code name} is
<jk>null</jk> or blank.
*/
@@ -500,7 +500,7 @@ public class ContentSecurityPolicy extends HttpStringHeader
{
var n = name.trim();
if (n.isEmpty())
throw iaex("directive name must not be blank");
- directives.put(n, new
ArrayList<>(Arrays.asList(sources)));
+ directives.put(n, sources == null ? new ArrayList<>() :
new ArrayList<>(Arrays.asList(sources)));
return this;
}
diff --git
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta.java
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta.java
index d9e1e23cbd..4306a098ce 100644
---
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta.java
+++
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta.java
@@ -347,7 +347,7 @@ public final class RrpcInterfaceMethodMeta {
* @return The default value, or <jk>null</jk>.
*/
public String getHeaderDefault(String name) {
- return headerDefaults.get(name);
+ return name == null ? null : headerDefaults.get(name);
}
/**
@@ -357,7 +357,7 @@ public final class RrpcInterfaceMethodMeta {
* @return The default value, or <jk>null</jk>.
*/
public String getQueryDefault(String name) {
- return queryDefaults.get(name);
+ return name == null ? null : queryDefaults.get(name);
}
/**
@@ -367,7 +367,7 @@ public final class RrpcInterfaceMethodMeta {
* @return The default value, or <jk>null</jk>.
*/
public String getFormDataDefault(String name) {
- return formDataDefaults.get(name);
+ return name == null ? null : formDataDefaults.get(name);
}
/**
@@ -377,7 +377,7 @@ public final class RrpcInterfaceMethodMeta {
* @return The default value, or <jk>null</jk>.
*/
public String getPathDefault(String name) {
- return pathDefaults.get(name);
+ return name == null ? null : pathDefaults.get(name);
}
/**
diff --git
a/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/header/ContentSecurityPolicy_Test.java
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/header/ContentSecurityPolicy_Test.java
index 4a82a5e576..e6835e3515 100644
---
a/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/header/ContentSecurityPolicy_Test.java
+++
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/header/ContentSecurityPolicy_Test.java
@@ -146,6 +146,13 @@ class ContentSecurityPolicy_Test extends TestBase {
@Test void b10_blankDirectiveNameThrows() {
assertThrowsWithMessage(IllegalArgumentException.class,
"directive name must not be blank", () -> create().directive(" ", SELF));
}
+
+ @Test void b11_nullSourcesRendersBareDirective() {
+ // Regression: directive(name, (String[])null) NPE'd on
Arrays.asList((String[])null); a null sources
+ // array must be treated the same as empty (renders the
bare directive name).
+ var x = create().directive("upgrade-insecure-requests",
(String[])null).build();
+ assertEquals("upgrade-insecure-requests", x);
+ }
}
//------------------------------------------------------------------------------------------------------------------
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta_Test.java
similarity index 56%
copy from
juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
copy to
juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta_Test.java
index 854e75ea81..ca25bf00cb 100644
---
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/beans/ResourceDescription_Test.java
+++
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta_Test.java
@@ -14,19 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.juneau.rest.server.beans;
+package org.apache.juneau.http.remote;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.juneau.*;
-import org.apache.juneau.marshall.marshaller.*;
import org.junit.jupiter.api.*;
-class ResourceDescription_Test extends TestBase {
+/**
+ * Validates {@link RrpcInterfaceMethodMeta}.
+ */
+class RrpcInterfaceMethodMeta_Test extends TestBase {
+
+ // Regression:
getHeaderDefault/getQueryDefault/getFormDataDefault/getPathDefault(null) NPE'd
because they
+ // delegated straight into a null-hostile Map.of()-backed map, despite
each documenting an "or null" @return.
+ @Test void getXxxDefault_nullName() throws Exception {
+ var m = Object.class.getMethod("toString");
+ var meta = new RrpcInterfaceMethodMeta(m, "POST", "/x",
RemoteReturn.BODY);
- @Test void a01_basic() throws Exception {
- var rd = new ResourceDescription("a","b?c=d&e=f","g");
- assertEquals("<table><tr><td>name</td><td><a
href=\"/b?c=d&e=f\">a</a></td></tr><tr><td>description</td><td>g</td></tr></table>",
Html.of(rd));
- assertEquals("{name:'a',description:'g'}", Json5.of(rd));
+ assertNull(meta.getHeaderDefault(null));
+ assertNull(meta.getQueryDefault(null));
+ assertNull(meta.getFormDataDefault(null));
+ assertNull(meta.getPathDefault(null));
}
-}
\ No newline at end of file
+}
diff --git
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java
index 519f66b43a..64bdfeb4e0 100644
---
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java
+++
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java
@@ -65,7 +65,7 @@ public class McpServerConfig {
/**
* MCP protocol revision returned by {@code initialize}.
*
- * @return The protocol version.
+ * @return The protocol version, or <jk>null</jk> if not set.
*/
public String getProtocolVersion() {
return protocolVersion;
@@ -74,7 +74,7 @@ public class McpServerConfig {
/**
* Sets the protocol revision.
*
- * @param protocolVersion The new value.
+ * @param protocolVersion The new value. Can be <jk>null</jk>.
* @return This object (for method chaining).
*/
public McpServerConfig setProtocolVersion(String protocolVersion) {
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/arg/CookieList.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/arg/CookieList.java
index 1e1c9e91a4..57b2c95438 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/arg/CookieList.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/arg/CookieList.java
@@ -38,7 +38,9 @@ public class CookieList extends ArrayList<Cookie> {
/**
* Static creator.
*
- * @param values The values to set in the cookie list. Must not be
<jk>null</jk>.
+ * @param values
+ * The values to set in the cookie list.
+ * <br>Can be <jk>null</jk> (e.g. from {@link
jakarta.servlet.http.HttpServletRequest#getCookies()} on a cookie-less
request), treated as an empty list.
* @return A new cookie list.
*/
public static CookieList of(Cookie[] values) {
@@ -48,9 +50,11 @@ public class CookieList extends ArrayList<Cookie> {
/**
* Constructor.
*
- * @param values The values to set in the cookie list. Must not be
<jk>null</jk>.
+ * @param values
+ * The values to set in the cookie list.
+ * <br>Can be <jk>null</jk> (e.g. from {@link
jakarta.servlet.http.HttpServletRequest#getCookies()} on a cookie-less
request), treated as an empty list.
*/
public CookieList(Cookie[] values) {
- super(l(values));
+ super(values == null ? l() : l(values));
}
}
\ No newline at end of file
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
index 4d5322a1d2..8140424327 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
@@ -143,7 +143,7 @@ public class FluentRequestHeaderAssertion<R> extends
FluentStringAssertion<R> {
* used on the same assertion.
*/
public FluentRequestHeaderAssertion(Assertion creator, RequestHeader
value, R returns) {
- super(null, value.asString().orElse(null), returns);
+ super(creator, value.asString().orElse(null), returns);
this.value = value;
setThrowable(BadRequest.class);
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/beans/ResourceDescription.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/beans/ResourceDescription.java
index a8573c9367..7ddf128293 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/beans/ResourceDescription.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/beans/ResourceDescription.java
@@ -77,7 +77,8 @@ public class ResourceDescription implements
Comparable<ResourceDescription> {
@Override /* Overridden from Comparable */
public int compareTo(ResourceDescription o) {
- return getName().compareTo(o.getName());
+ // Null-safe: the no-arg ctor and null-accepting ctors leave
'name' null (consistent with the null-safe equals()).
+ return cmp(getName(), o.getName());
}
/**
@@ -120,7 +121,8 @@ public class ResourceDescription implements
Comparable<ResourceDescription> {
@Override /* Overridden from Object */
public int hashCode() {
- return getName().hashCode();
+ // Null-safe: the no-arg ctor and null-accepting ctors leave
'name' null (consistent with the null-safe equals()).
+ return getName() == null ? 0 : getName().hashCode();
}
/**
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/BasicDebugEnablement.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/BasicDebugEnablement.java
index a2ef2e5f5a..f66d7c1fa2 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/BasicDebugEnablement.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/BasicDebugEnablement.java
@@ -38,7 +38,7 @@ import org.apache.juneau.rest.server.*;
* <ul>
* <li class='ja'>{@link Rest#debug()}
* <li class='ja'>{@link RestOp#debug()}
- * <li class='ja'>{@link Rest#debug()}
+ * <li class='jm'>{@link Debug#on()}
* </ul>
*
* <h5 class='section'>See Also:</h5><ul>
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/DebugConfig.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/DebugConfig.java
index f55b2cadc6..de66fd744a 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/DebugConfig.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/debug/DebugConfig.java
@@ -219,6 +219,6 @@ public class DebugConfig {
* @return The configured rule, or <jk>null</jk>.
*/
public DebugRule getRuleFor(String target) {
- return rules.get(target);
+ return target == null ? null : rules.get(target);
}
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestAttributes.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestAttributes.java
index 6815bfcb7b..db58c6b7ff 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestAttributes.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestAttributes.java
@@ -129,6 +129,8 @@ public class RequestAttributes {
* @return This object.
*/
public RequestAttributes addDefault(List<NamedAttribute> pairs) {
+ if (pairs == null)
+ return this;
for (var p : pairs)
if (sreq.getAttribute(p.getName()) == null) {
Object o = p.getValue();
@@ -158,6 +160,8 @@ public class RequestAttributes {
* @return This object.
*/
public RequestAttributes addDefault(NamedAttributeMap pairs) {
+ if (pairs == null)
+ return this;
for (var p : pairs.values())
if (sreq.getAttribute(p.getName()) == null) {
Object o = p.getValue();
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
index bf521d7fc3..c64c74fb9e 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
@@ -279,6 +279,8 @@ public class RequestFormParamList extends
ArrayList<RequestFormParam> {
* @return This object.
*/
public RequestFormParamList addDefault(List<? extends HttpPart> pairs) {
+ if (pairs == null)
+ return this;
for (var p : pairs) {
var name = p.getName();
var l = stream(name);
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestPathParamList.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestPathParamList.java
index d0abf95e19..2c1d149f92 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestPathParamList.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestPathParamList.java
@@ -224,6 +224,8 @@ public class RequestPathParamList extends
ArrayList<RequestPathParam> {
* @return This object.
*/
public RequestPathParamList addDefault(List<HttpPart> pairs) {
+ if (pairs == null)
+ return this;
for (var p : pairs) {
var name = p.getName();
var l = stream(name);
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList.java
index f227078012..e8d3051ef8 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestQueryParamList.java
@@ -239,6 +239,8 @@ public class RequestQueryParamList extends
ArrayList<RequestQueryParam> {
* @return This object.
*/
public RequestQueryParamList addDefault(List<? extends HttpPart> pairs)
{
+ if (pairs == null)
+ return this;
for (var p : pairs) {
var name = p.getName();
var l = stream(name);
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/SwaggerException.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/SwaggerException.java
index 41ad6cae6f..7dcb95d2f3 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/SwaggerException.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/SwaggerException.java
@@ -16,8 +16,6 @@
*/
package org.apache.juneau.rest.server.swagger;
-import static org.apache.juneau.commons.utils.Shorts.*;
-
import org.apache.juneau.marshall.parser.*;
@SuppressWarnings({
@@ -27,6 +25,8 @@ class SwaggerException extends ParseException {
private static final long serialVersionUID = 1L;
SwaggerException(Exception e, String location, Object...locationArgs) {
- super(e, "Swagger exception: at " + f(location, locationArgs));
+ // Format exactly once (via the parent ctor) so a literal '%'
surviving from location/locationArgs
+ // can't be re-interpreted as a printf directive on a second
pass.
+ super(e, "Swagger exception: at " + location, locationArgs);
}
}
\ No newline at end of file
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/widget/MenuItemWidget.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/widget/MenuItemWidget.java
index 2a98d754c4..e0a1a2f865 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/widget/MenuItemWidget.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/widget/MenuItemWidget.java
@@ -60,7 +60,7 @@ public abstract class MenuItemWidget extends Widget {
*
* <p class='bjava'>
* <ja>@Override</ja>
- * <jk>public</jk> String getBeforeShowScript(RestRequest
<jv>req</jv>) {
+ * <jk>public</jk> String getBeforeShowScript(RestRequest
<jv>req</jv>, RestResponse <jv>res</jv>) {
* <jk>return</jk> <js>""</js>
* + <js>"\n var xhr = new
XMLHttpRequest();"</js>
* + <js>"\n xhr.open('GET',
'/petstore/pet?s=status=AVAILABLE&v=id,name', true);"</js>
@@ -88,8 +88,8 @@ public abstract class MenuItemWidget extends Widget {
*
* <p class='bjava'>
* <ja>@Override</ja>
- * <jk>public</jk> String getBeforeShowScript(RestRequest
<jv>req</jv>) <jk>throws</jk> Exception {
- * <jk>return</jk>
loadScript(<js>"AddOrderMenuItem_beforeShow.js"</js>);
+ * <jk>public</jk> String getBeforeShowScript(RestRequest
<jv>req</jv>, RestResponse <jv>res</jv>) <jk>throws</jk> Exception {
+ * <jk>return</jk> loadScript(<jv>req</jv>,
<js>"AddOrderMenuItem_beforeShow.js"</js>);
* }
* </p>
*
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/arg/CookieList_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/arg/CookieList_Test.java
new file mode 100644
index 0000000000..90cf363065
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/arg/CookieList_Test.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.arg;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+import jakarta.servlet.http.*;
+
+/**
+ * Validates {@link CookieList}.
+ *
+ * <p>
+ * Regression: the constructor did {@code super(l(values))}, and {@code
l(null)} returns {@code null}, so
+ * {@code new CookieList(null)} threw a {@link NullPointerException}. {@code
HttpServletRequest.getCookies()}
+ * returns {@code null} for a cookie-less request (servlet spec), so any
{@code @RestOp} method declaring a
+ * {@code CookieList} parameter NPE'd on a request with no cookies.
+ */
+class CookieList_Test extends TestBase {
+
+ @Test void a01_nullCookiesYieldsEmptyList() {
+ assertTrue(CookieList.of(null).isEmpty());
+ assertTrue(new CookieList(null).isEmpty());
+ }
+
+ @Test void a02_populatedCookiesPreserved() {
+ var l = CookieList.of(new Cookie[]{new Cookie("a", "1"), new
Cookie("b", "2")});
+ assertEquals(2, l.size());
+ assertEquals("a", l.get(0).getName());
+ assertEquals("b", l.get(1).getName());
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion_Test.java
new file mode 100644
index 0000000000..0061b9395e
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion_Test.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.assertions;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.server.httppart.*;
+import org.apache.juneau.test.assertions.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Validates {@link FluentRequestHeaderAssertion}.
+ *
+ * <p>
+ * Regression: the chained constructor used to hard-code {@code super(null,
...)}, discarding its {@code creator}
+ * argument, so config (custom failure message, output stream, etc.) set on
the creating assertion was lost when a
+ * header assertion was built via the chained constructor. The sibling
query/form-param assertions correctly pass
+ * {@code super(creator, ...)}.
+ */
+class FluentRequestHeaderAssertion_Test extends TestBase {
+
+ @Test void a01_creatorMsgPropagates() {
+ var header = mock(RequestHeader.class);
+ when(header.asString()).thenReturn(Optional.of("actual"));
+
+ // The custom failure message configured on the creating
assertion must propagate to the header assertion
+ // (it did not before the fix, because the chained ctor
hard-coded super(null, ...)).
+ var creator = new FluentStringAssertion<>("ignored",
null).setMsg("CUSTOM {msg}");
+ var h = new FluentRequestHeaderAssertion<>(creator, header,
null);
+
+ var e = assertThrows(RuntimeException.class, () ->
h.is("expected"));
+ assertTrue(e.getMessage().startsWith("CUSTOM "), () -> "Message
did not carry creator config: " + e.getMessage());
+ }
+
+ @Test void a02_noCreatorUsesDefaultMsg() {
+ var header = mock(RequestHeader.class);
+ when(header.asString()).thenReturn(Optional.of("actual"));
+
+ var h = new FluentRequestHeaderAssertion<>(header, null);
+
+ var e = assertThrows(RuntimeException.class, () ->
h.is("expected"));
+ assertFalse(e.getMessage().startsWith("CUSTOM "));
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/swagger/SwaggerException_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/swagger/SwaggerException_Test.java
new file mode 100644
index 0000000000..fffce32af1
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/swagger/SwaggerException_Test.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.swagger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Validates {@link SwaggerException} message formatting.
+ *
+ * <p>
+ * Regression: the constructor used to pre-format the location with {@code
f(...)} and then pass the concatenated
+ * result to a printf constructor, giving a second format pass on which a
surviving literal {@code '%'} could be
+ * re-interpreted as a format directive. The message must now be formatted
exactly once.
+ */
+class SwaggerException_Test extends TestBase {
+
+ @Test void a01_singleFormatPass() {
+ var ex = new SwaggerException(null, "Unexpected data type
'%s'.", "Map");
+ assertEquals("Swagger exception: at Unexpected data type
'Map'.", ex.getMessage());
+ }
+
+ @Test void a02_literalPercentInArgSurvives() {
+ // A literal '%' inside an argument value must be substituted
verbatim and must not throw.
+ var ex = new SwaggerException(null, "Unexpected data type
'%s'.", "50% off");
+ assertEquals("Swagger exception: at Unexpected data type '50%
off'.", ex.getMessage());
+ }
+
+ @Test void a03_causePreserved() {
+ var cause = new IllegalStateException("nope");
+ var ex = new SwaggerException(cause, "In %s.", "location");
+ assertSame(cause, ex.getCause());
+ assertEquals("Swagger exception: at In location.",
ex.getMessage());
+ }
+}