The Nuxeo Platform marshalling service provides a rendering context. This context allows you to parametrize your JSON marshallers. For example you can decide to add a parameter which tells your marshaller to fetch or not some parts of the rendered object.
Usages
This context is managed by the class org.nuxeo.ecm.core.io.registry.context.RenderingContext
. A helper is provided to easily create a context: org.nuxeo.ecm.core.io.registry.context.RenderingContext.CtxBuilder
.
When the marshalling service is used from JAX-RS, the context is filled with:
- The Nuxeo URL
- The client locale
- The given HTTP headers
- The GET parameters
- The Java Request attributes
If you add parametrized behaviors to your marshaller, it is then customizable by the end-user application using the corresponding parameter in headers or in GET parameters.
@Setup(mode = SINGLETON, priority = Priorities.REFERENCE)
public class ProductJsonWriter extends AbstractJsonWriter<Product> {
public void write(Product product, JsonGenerator jg) throws IOException {
jg.writeStartObject();
jg.writeNumberField("ref", product.getReference());
if (ctx.getBooleanParameter("loadProductDescription")) {
jg.writeStringField("desc", product.getDescription());
}
String url = ctx.getBaseUrl() + "/productApp/product/ref/" + product.getReference();
jg.writeStringField("url", url);
jg.writeEndObject();
}
}
You can send the parameters using HTTP GET parameter or HTTP header (useful for POST/PUT/DELETE results).
$ curl -X GET 'http://NUXEO_SERVER/nuxeo/site/productApp/product/ref/10001?loadProductDescription=true'
$ curl -X GET -H 'loadProductDescription:true' 'http://NUXEO_SERVER/nuxeo/site/productApp/product/ref/10001'
You can also use the rendering context from the server side using the CtxBuilder
.
Product product = ...;
RenderindContext ctx = CtxBuilder.param("loadProductDescription", Boolean.TRUE).get();
String json = MarshallingHelper.objectToJson(product, ctx);
Finally, you can force the value of a parameter on the server side by overriding the rendering context used to produce the response.
@GET
@Produces(MediaType.APPLICATION_JSON)
public Product doGet(@Context HttpServletRequest request) {
Product product;
// get the product ... and configure output
RenderindContext ctx = CtxBuilder.param("loadProductDescription", Boolean.TRUE).get();
request.setAttribute(CTX_KEY, ctx);
return product;
}
private static final String CTX_KEY = "_STORED_GENERATED_RENDERING_CONTEXT";
RenderingContext API
Setting/Getting the Nuxeo Base URL
Web context usage | Retrieved from the HTTP call |
---|---|
Server side usage |
ctx = CtxBuilder.base("http://server/nuxeo").get(); |
Use from marshallers |
String url = ctx.getBaseUrl(); |
Setting/Getting the Current Locale
Web context usage | Retrieved from the end-user's browser |
---|---|
Server side usage |
ctx = CtxBuilder.locale(Locale.FRENCH).get(); |
Use from marshallers |
Locale locale = ctx.getLocale(); |
Delivering/Using a CoreSession to/in Marshallers
Web context usage | Retrieved from the WebContext, or from a document or created from a nxrepository parameter or created from a Repository header. |
---|---|
Server side usage |
CoreSession session = ...; ctx = CtxBuilder.session(session).get(); |
Use from marshallers |
The try-resource statement ensure the session would be closed if it's necessary
try (SessionWrapper wrapper = ctx.getSession(document)) {CoreSession session = wrapper.getSession(); } try (SessionWrapper wrapper = ctx.getSession(null)) {CoreSession session = wrapper.getSession(); } |
Setting/Getting Any Parameter
Web context usage |
URL:
|
---|---|
Server side usage |
ctx = CtxBuilder.param("paramName", "paramValue").get(); ctx.setParameterValues("paramName", "paramValue"); |
Use from marshallers |
Object o = ctx.getParameter("paramName"); List<Object> list = ctx.getParameters("paramName"); boolean b = ctx.getBooleanParameter("paramName"); |
Loading Some Document Properties
Web context usage |
URL:
|
---|---|
Server side usage |
ctx = CtxBuilder.properties("*").get(); ctx = CtxBuilder.properties("dublincore", "file").get(); ctx.setParameterValues("properties", "dublincore", "file"); |
Use from marshallers |
Set<String> properties = ctx.getProperties(); |
Loading Additional Parts of an Object Whose entity-type Is 'objectType'
The corresponding behavior depends on the marshaller.
Web context usage |
URL:
|
---|---|
Server side usage | Only for documents
ctx = CtxBuilder.fetchInDoc("part1", "part2").get(); ctx = CtxBuilder.fetch("objectType", "part1", "part2").get(); ctx.setParameterValues("fetch.objectType", "part1", "part2"); |
Use from marshallers |
Set<String> toLoad = ctx.getFetched("objectType"); |
Enabling Enricher for an objectType
The enricher will contribute to the object marshalling. The object marshaller must extend ExtensibleEntityJsonWriter
. The given enricher name must match an existing enricher for this object type.
Web context usage |
URL:
|
---|---|
Server side usage | Only for documents
ctx = CtxBuilder.enrichDoc("children", "acl").get(); ctx = CtxBuilder.enrich("objectType", "children", "acl").get(); ctx.setParameterValues("enrichers.objectType", "children", "acl"); |
Use from marshallers |
Set<String> enricherToActivate = ctx.getEnrichers("objectType"); |
Translating Some Part of an Object Which May Contains Some l10n Key
Web context usage |
URL:
|
---|---|
Server side usage |
ctx = CtxBuilder.translate("objectType", "elementToTranslate").get(); ctx.setParameterValues("translate.objectType", "elementToTranslate"); |
Use from marshallers |
Set<String> toTranslate = ctx.getTranslated("objectType"); |
Controlling the Aggregation Depth
Available values are: root for no aggregation, children for 1 level, max for 2 levels. Default value is 'children'. See next chapter for more details
Web context usage |
URL:
|
---|---|
Server side usage |
ctx = CtxBuilder.depth(DepthValues.children).get(); ctx.setParameterValues("depth", "children"); |
Use from marshallers |
try (Closeable resource = ctx.wrap().controlDepth().open()) { // call another marshaller } catch (MaxDepthReachedException e) { // do not load properties } |
Aggregating Marshallers and Avoiding Infinite Loops
Let's take an example. We created two classes, Product
and Category
. And we'd like to marshall them as JSON.
When loading a category, we'd like to be able to get the corresponding products.
@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class CategoryJsonWriter extends AbstractJsonWriter<Category> {
public void write(Category category, JsonGenerator jg) throws IOException {
jg.writeStartObject();
jg.writeNumberField("ref", category.getReference());
jg.writeStringField("desc", category.getDescription());
if (ctx.getFetched("category").contains("products")) {
jg.writeArrayFieldStart("products");
for (Product product: product.getProducts()) {
// delegates the marshalling to Nuxeo
writeEntity(product);
}
jg.writeEndArray();
}
jg.writeEndObject();
}
}
$ curl -X GET 'http://NUXEO_SERVER/nuxeo/site/productApp/category/ref/12?fetch.category=products'
When loading a product, we'd like to be able to get the corresponding categories.
@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class ProductJsonWriter extends AbstractJsonWriter<Product> {
public void write(Product product, JsonGenerator jg) throws IOException {
jg.writeStartObject();
jg.writeNumberField("ref", product.getReference());
jg.writeStringField("desc", product.getDescription());
if (ctx.getFetched("product").contains("categories")) {
jg.writeArrayFieldStart("categories");
for (Category category: product.getCategories()) {
// delegates the marshalling to Nuxeo
writeEntity(category);
}
jg.writeEndArray();
}
jg.writeEndObject();
}
}
$ curl -X GET 'http://NUXEO_SERVER/nuxeo/site/productApp/category/ref/12?fetch.product=categories'
This works fine. But if we want to get both product's categories and category's products, there's a real risk to get an infinite loop. Or at least to get a very huge amount of JSON (if the category-product graph has no cycle).
$ curl -X GET 'http://NUXEO_SERVER/nuxeo/site/productApp/category/ref/12?fetch.product=categories&fetch.category=products'
To manage this case, we could use a counter put in the context but this will not work in most case because the counter will manage the first "line" of marshaller and disable the others: that's due to the processing which follows a "Depth First Search".
The Nuxeo Platform provides a specific object to wrap the depth control in each "crossing line". Each time you call another marshaller, you should use it.
@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class CategoryJsonWriter extends AbstractJsonWriter<Category> {
public void write(Category category, JsonGenerator jg) throws IOException {
jg.writeStartObject();
jg.writeNumberField("ref", category.getReference());
jg.writeStringField("desc", category.getDescription());
// control the graph depth
try (Closeable resource = ctx.wrap().controlDepth().open()) {
if (ctx.getFetched("category").contains("products")) {
jg.writeArrayFieldStart("products");
for (Product product: product.getProducts()) {
// delegates the marshalling to Nuxeo
writeEntity(product);
}
jg.writeEndArray();
}
} catch (MaxDepthReachedException e) {
// do not load products
}
jg.writeEndObject();
}
}
The max depth is controlled by the depth
parameter in the rendering context. The default value for depth is children
.
Depth values: If we get a product -> its categories -> their products ...
- root: No category will be loaded, just the first product.
- children: The product is loaded with its categories.
- max: The product is loaded, its categories also, and their products.
The only way to load more data is to avoid the use of depth control (which is bad!).
Each existing Nuxeo writer/enricher, which may use another marshaller, uses the depth's control.