Object Tools
The org.apache.juneau.marshall.objecttools package defines convenience utility classes for accessing and manipulating POJOs.
It consists of the following classes:
ObjectSearcher, ObjectSorter, ObjectViewer and ObjectPaginator were removed in 10.0. Use BeanQuery instead: build an InMemoryBeanQueryContext for your row type, open a session over your rows, and call find(query) (bean rows) or findValues(query) (projected maps). The Queryable REST converter is built on the same engine; see Converters and the V10 Migration Guide.
ObjectRest (Removed in 10.0)
The public ObjectRest class was removed in Juneau 10.0. Its URL-addressed GET/PUT/POST/DELETE traversal engine survives
as an internal PathTraversal helper and is not a supported public API.
The path-addressed node operations that ObjectRest provided are now available directly on the collections classes, and a new
typed tree façade plus RFC 6901 JSON-Pointer surface covers the same ground:
- Slash-delimited path navigation — use the unchanged MarshalledMap /
MarshalledList methods
getAt/putAt/postAt/deleteAt(also available onJsonMap/JsonList). These behave exactly as before — they are now backed by the internalPathTraversalhelper. - RFC 6901 JSON-Pointer addressing — use
MarshalledMap.at(String)/MarshalledList.at(String), the new MarshalledNode typed tree façade (at/find/set/remove), or the standalone JsonPointer helper. See the Tree Model & RFC 6901 JSON-Pointer page.
JsonMap map = Json.to("{name:'John Smith',address:{streetAddress:'21 2nd Street'},phoneNumbers:['212 555-1111']}", JsonMap.class);
// OLD: ObjectRest johnSmith = ObjectRest.create(map);
// String name = johnSmith.getString("name");
// String street = johnSmith.getString("address/streetAddress");
// NEW (unchanged slash-path navigation):
String name = map.getAt("name", String.class);
String street = map.getAt("address/streetAddress", String.class);
map.putAt("address/streetAddress", "101 Cemetery Way");
map.postAt("phoneNumbers", "212 555-3333");
map.deleteAt("name");
// NEW (RFC 6901 JSON-Pointer):
String street2 = map.at("/address/streetAddress").asString();
The reflective ObjectRest.invokeMethod(...) / getPublicMethods(...) helpers were dropped — use
ObjectIntrospector directly if you need that
capability. The companion exception was renamed to
PathTraversalException, and the
Traversable REST response
converter is unaffected.
ObjectIntrospector
The ObjectIntrospector class is used to invoke
methods on Objects using arguments in serialized form.
As of Juneau 10.0, ObjectIntrospector denies reflective method dispatch by default. Before calling
invokeMethod(...), you must explicitly allow-list the method(s) that may be invoked via allow(Class, String...)
(or the Predicate<Method> form of allow(...)). Invoking a method that isn't allow-listed throws
MethodNotAllowlistedException.
This closes off "reflective-invoke-over-the-wire" exposure — e.g. a REST endpoint that fed an untrusted method name/arguments string straight into an introspector could previously be used to invoke any public, non-deprecated method on the target object.
See the Release Notes for the full migration note.
String string1 = "foobar";
String string2 = ObjectIntrospector
.create(string1)
.allow(String.class, "substring(int,int)") // Explicit allow-list.
.invokeMethod(String.class, "substring(int,int)", "[3,6]"); // "bar"
For trusted, in-process-only use, allowAll() can be used instead of an explicit allow-list to restore the
pre-10.0 behavior of dispatching to any public, non-deprecated method:
String string2 = ObjectIntrospector
.create(string1)
.allowAll() // Trusted callers only - never use with untrusted method/argument sources.
.invokeMethod(String.class, "substring(int,int)", "[3,6]"); // "bar"
The arguments passed to the identified method are POJOs serialized in JSON format. Arbitrarily complex arguments can be passed in as arguments.
This class is used in the Introspectable REST
response converter. Introspectable is deny-by-default as shipped — with no configuration, every
invokeMethod request fails with an HTTP 500 (MethodNotAllowlistedException). A resource opts specific
methods in by registering an
IntrospectableSettings
bean in its bean store — see Introspectable REST converter below.
Introspectable REST converter
The Introspectable converter
adapts ObjectIntrospector for REST — it lets a caller invoke public methods on a @RestOp method's response
object via the invokeMethod/invokeArgs query parameters.
As shipped, Introspectable has no methods allow-listed — every invokeMethod request is refused with an
HTTP 500 (MethodNotAllowlistedException). A resource opts specific methods in explicitly by registering an
IntrospectableSettings
bean in its bean store:
@Rest(converters=Introspectable.class)
public class MyResource extends BasicRestServlet {
@Bean
public IntrospectableSettings introspectableSettings() {
return IntrospectableSettings.create()
.allow(MyBean.class, "getName", "getAge")
.build();
}
@RestGet
public MyBean get() {
return new MyBean();
}
}
A request to /?invokeMethod=getName now dispatches to MyBean.getName(); any other method (e.g. toString)
is still refused, since it wasn't allow-listed.
For a trusted resource that needs the pre-10.0 behavior of dispatching to any public, non-deprecated method,
call allowAll() instead:
@Bean
public IntrospectableSettings introspectableSettings() {
return IntrospectableSettings.create().allowAll().build();
}
Never call allowAll() on a resource whose response objects expose methods you wouldn't want an arbitrary
caller to invoke — the method name and arguments for Introspectable always come from untrusted request
query parameters. Allow-list the specific methods instead.
This is an extremely powerful but potentially dangerous tool. Use wisely.
ObjectMerger
The ObjectMerger class is used for merging POJOs behind a single interface. This is particularly useful in cases where you want to define beans with 'default' values. For example, given the following bean classes:
public interface IA {
String getX();
void setX(String x);
}
public class A implements IA {
private String x;
public A(String x) {
this.x = x;
}
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
}
The getters will be called in order until the first non-null value is returned:
merge = ObjectMerger.merge(IA.class, new A("1"), new A("2"));
assertEquals("1", merge.getX());
merge = ObjectMerger.merge(IA.class, new A(null), new A("2"));
assertEquals("2", merge.getX());
merge = ObjectMerger.merge(IA.class, new A(null), new A(null));
assertEquals(null, merge.getX());