Converters
Converters can be thought of as "post-processors" for response POJOs before they get passed to the serializer.
Converters are associated with resource classes and methods via the following:
// GET person request handler.
// Traversable conversion enabled to allow nodes in returned POJO tree to be addressed.
// Queryable conversion enabled to allow returned POJO to be searched/viewed/sorted.
@RestGet(
path="/people/{id}/*",
converters={Traversable.class,Queryable.class}
)
public Person getPerson(@Path("id") int id) {
return findPerson(id);
}
Juneau defines the following converters out-of-the-box:
Queryable (BeanQuery)
As of 10.0, Queryable runs on the shared BeanQuery engine. A response that is a Collection or array of beans (or of Maps) can be searched, projected, sorted and paged with these request parameters, which are bound by BeanQueryRequest:
| Parameter | Meaning |
|---|---|
search | Comma-separated column=expression clauses, e.g. name=$contains(smith),age=$gte(18) |
view | Comma-separated columns to return, in order. Rows come back as maps of those columns |
sort | Comma-separated sort columns; append :desc (or -) for descending |
position | 0-based index of the first row (negative is HTTP 400) |
limit | Maximum rows. Defaults to defaultLimit (100) and is clamped to maxLimit (1000) |
opts | key=value options, e.g. counts=true |
GET /people?search=age=$gte(18)&sort=name,age:desc&view=name,age&position=20&limit=10
A response that is not tabular (not a collection or array, or whose elements are plain strings, numbers, dates or other scalars) is returned unchanged and the parameters are ignored. An empty collection is also returned unchanged. A bean type gets one cached context per element class; Map rows get a context per request, with one column per key of the first row (types inferred from that row's values).
A bad column or option (for example ?sort=noSuchColumn) throws BeanQuerySyntaxException, which the server maps to HTTP 400 with an X-BeanQuery-Error: UNKNOWN_COLUMN header (the BeanQuerySyntaxException.Code name) and, with problem details on, a "code" member in the body. A BeanQueryExecutionException becomes HTTP 500 with the body Query execution failed.; the cause is logged, not sent. See BeanQuery: REST binding.
?opts=counts=true adds count headers. The body stays a bare list, and each header is set only when that count was computed:
GET /people?search=age=$gte(18)&opts=counts=true
X-BeanQuery-Total: 42
X-BeanQuery-Matched: 7
QueryableSettings
Register a QueryableSettings bean to tune the engine per resource. Each setting you leave unset keeps the BeanQuery default, so the defaults are not duplicated here.
@Rest(converters=Queryable.class)
public class PeopleResource extends BasicRestServlet {
@Bean
public QueryableSettings queryableSettings() {
// $regex is allowed by default; allowRegex(false) opts this resource out.
return QueryableSettings.create().defaultLimit(20).maxLimit(500).build();
}
@RestGet("/people")
public List<Person> getPeople() {...}
}
| Setting | Default | Meaning |
|---|---|---|
defaultLimit(int) | 100 | Page size when the request has no limit |
maxLimit(Integer) | 1000 | Largest page size; larger requests are clamped (null = no cap) |
allowRegex(boolean) | true | The $regex operator; false makes it a 400 (REGEX_DISABLED) |
regexTimeout(Duration) | 50 ms | Time budget for all regex matching in one query |
countPolicy(CountPolicy) | IF_REQUESTED | Which counts are computed |
maxSearchClauses / maxSortKeys / maxSearchLength / maxExpressionDepth | 64 / 8 / 4096 / 16 | Caps on the request |
All of these are trusted server-side settings; none is ever read from request data.
Your own list operation
Without the converter, take a BeanQueryRequest argument and run the engine yourself. The argument binds all six parameters and documents them in Swagger automatically:
private static final InMemoryBeanQueryContext<Person> PEOPLE = InMemoryBeanQueryContext.create(Person.class).build();
@RestGet("/people")
public List<Person> getPeople(BeanQueryRequest q) {
try (var s = PEOPLE.getSession(people)) {
return s.find(q).rows();
}
}
A concrete @Request class like BeanQueryRequest must have a public no-arg constructor and a setter for every annotated getter; this is checked when the operation table is built (see the V10 Migration Guide).
DataTables server-side processing
The optional juneau-console-datatables module ships a DataTablesRequest bean that models the DataTables server-side request (draw/start/length/search/order/columns), and a DataTablesQuery adapter that maps it onto a BeanQuery session and returns a DataTablesResults envelope.
private static final InMemoryBeanQueryContext<MyRow> ROWS = InMemoryBeanQueryContext.create(MyRow.class).build();
@RestPost(path="/rows")
public DataTablesResults<MyRow> getRows(@Content DataTablesRequest request) {
try (var session = ROWS.getSession(loadRows())) {
return DataTablesQuery.run(request, session);
}
}
The caller owns the session, so the same adapter works over the in-memory engine or a SQL-backed one. A malformed search expression throws BeanQuerySyntaxException, which propagates to HTTP 400 with the X-BeanQuery-Error header; JuneauDataTables.ajax shows the server's message to the user. See the BeanQuery package topic for the engine itself.
Client-side helpers (DataTables)
The juneau-console-datatables module also ships thin, first-party browser-side helpers so a server-rendered table can be upgraded into a DataTables grid without hand-duplicating column lists or serving the wiring yourself:
This is an Apache project, and the DataTables library's license is not an ASF category-A license. Juneau therefore does not bundle DataTables' own JS/CSS (nor jQuery) — the only asset it ships is the thin first-party juneau-datatables.js glue. Reference the DataTables library from a CDN or self-host it, and wire it into the page yourself via @HtmlDocConfig. @HtmlDocConfig is resolved per endpoint / per resource-class hierarchy — a mixin cannot inject page assets into your other endpoints — so add the wire-points to the resource (or op) that renders the table:
@Rest(path="/releases", mixins=DataTablesMixin.class)
@HtmlDocConfig(
stylesheet="$S{juneau.datatables.cssUrl:https://cdn.datatables.net/2.1.8/css/dataTables.dataTables.min.css}",
head={
"<script src='$S{juneau.datatables.jqueryUrl:https://code.jquery.com/jquery-3.7.1.min.js}'></script>",
"<script src='$S{juneau.datatables.jsUrl:https://cdn.datatables.net/2.1.8/js/dataTables.min.js}'></script>",
"<script src='servlet:/juneau-datatables.js'></script>"
}
)
public class ReleasesResource extends BasicRestServlet {
@RestGet
public Table getReleases() {
return DataTablesTable.of("releases", releases, Release.class);
}
}
The URLs are SVL-resolvable, so the DataTables distribution can be overridden per environment without code changes.