Looking for Cloud Services or Professional Support? Check restheart.com

Request Transformers

Introduction

Transformers allow to transform the request or the response. For instance, Transformers can be used to:

  • filtering out from the response the password property of the /db/users resource
  • adding the filter={"visibility":"public"} query parameter to requests limiting the client visibility on documents.

The rts collection metadata

In RESTHeart, not only documents but also dbs and collections (and files buckets, schema stores, etc.) have properties. Some properties are metadata, i.e. have a special meaning for RESTheart that controls its behavior.

The metadata rts allows to declare transformers.

  • a transformer declared in the rts db property, gets executed to any requests that involves the db and its children resources (collections, documents, file buckets, schema stores, etc.).
  • a transformer declared in the rts collection property, gets executed to any requests that involves the collection and its documents.

rts is an array of transformer objects. A transformer object has the following format:

{"name":<name>, "phase":<phase>, "scope":<scope>, "args":<args>}
Property
Description
Mandatory
name

The name of the transformer

Yes
phase

specifies to transform either the request or the response.

Valid values are REQUEST or RESPONSE

Yes
scope

Only applicable to RESPONSE transformers; with "scope": "THIS" the transformer is executed once on the whole response, with "scope":"CHILDREN" it is executed once per embedded document.

When "phase": "RESPONSE"
args arguments to be passed to the transformer. No

Global Transformers

Global Transformers are applied to all requests.

Global Transformers can be defined programmatically instantiating GlobalTransformer objects:

    /**
     *
     * @param transformer
     * @param phase
     * @param scope
     * @param predicate transformer is applied only to requests that resolve
     * the predicate
     * @param args
     * @param confArgs
     */
    public GlobalTransformer(Transformer transformer,
            RequestContextPredicate predicate,
            RequestTransformer.PHASE phase,
            RequestTransformer.SCOPE scope,
            BsonValue args,
            BsonValue confArgs)

and adding them to the list TransformerHandler.getGlobalTransformers()

// a predicate that resolves GET /db/coll
RequestContextPredicate predicate = new RequestContextPredicate() {
            @Override
            public boolean resolve(HttpServerExchange hse, RequestContext context) {
                return context.isDb() && context.isGet();
            }
        };

// Let's use the predefined FilterTransformer to filter out properties from GET response
Transformer transformer = new FilterTransformer();

// FilterTransformer requires an array of properties to filter out as argument
BsonArray args = new BsonArray();
args.add(new BsonString("propToFilterOut"));

// if the checker requires configuration arguments, define them here
BsonDocument confArgs = null;

GlobalTransformer globalTransformer = new GlobalTransformer(
                transformer, 
                RequestTransformer.PHASE.RESPONSE, 
                RequestTransformer.SCOPE.CHILDREN, 
                args, 
                confArgs)

// finally add it to global checker list
TransformerHandler.getGlobalTransformers().add(globalTransformer);

You can use an Initializer to add Global Transformers.

Available and Custom Transformers

RESTHeart comes with some ready-to-be-used transformers:

  • addRequestProperties to add properties.
  • filterProperties to filter out properties.
  • stringsToOids and oidsToStrings to convert strings to ObjectIds and vice versa (usually from requests and responses the respectively).
  • writeResult that uses context.getDbOperationResult() to add to the response body the old and the new version of the modified document on write requests
  • hashProperties that allows to hash properties using bcrypt on write requests

Custom transformers can also be implemented.

Inject properties with addRequestProperties transformer

addRequestProperties is a transformer shipped with RESTHeart that allows to add some properties to the resource state.

The properties are specified via the args property of the transformer object. It is mainly intended to be applied to REQUESTs.

These properties are injected server side. If we need to store some information (such as the username) and we cannot rely on the client, this transformer is the solution.

An example is a blog application where each post document has the author property. This property could be valued server side via this transformer to avoid users to publish posts under a fake identity.

The properties that can be added are:

  • userName
  • userRoles
  • dateTime
  • localIp
  • localPort
  • localServerName
  • queryString
  • relativePath
  • remoteIp
  • requestMethod
  • requestProtocol

For instance, the following request creates the collection rtsexample with this transformer applying to documents in the REQUEST phase. Looking at the args argument we can figure out that the request json data will be transformed adding the log object with the username of authenticated user and its remote ip.

$ http -a a:a PUT 127.0.0.1:8080/test/rtsexample rts:='[{"name":"addRequestProperties","phase":"REQUEST","scope":"CHILDREN","args":{"log": ["userName","remoteIp"]}}]'

If we now create a document, we can see the log property stored in the document because it was injected by RESTHeart in the request data.

$ http -a a:a PUT 127.0.0.1:8080/test/rts/mydoc a:=1
HTTP/1.1 201 Created
...
 
$ http -a a:a GET 127.0.0.1:8080/test/rts/mydoc
HTTP/1.1 200 OK
...
{
    "_id": "mydoc", 
    "a": 1, 
    "log": {
        "remoteIp": "127.0.0.1", 
        "userName": "a"
    },
    ...
}

Filter out properties with filterProperties transformer

filterProperties is a transformer shipped with RESTHeart that allows to filter out a the properties specified via the args property of the transformer metadata object.

The usual application of this transformer is hiding sensitive data to clients.

In Security section, the DbIdentityManager is described. It allows to authenticate users defined in a mongodb collection that must have the following fields: _id as the userid, password (string) and roles (an array of strings).

Let’s say that the user collection is called userbase; we can remove the password to be sent back to clients with the following filterProperties transformer.

$ http -a a:a PUT 127.0.0.1:8080/test/userbase rts:='[{"name":"filterProperties", "phase":"RESPONSE", "scope":"CHILDREN", "args":["password"]}]'

Custom Transformers

A transformer is a java class that implements the interface org.restheart.metadata.transformers.Transformer.

It requires to implement the method transform() with 5 arguments:

  1. HttpServerExchange exchange
  2. RequestContext context (that is the suggested way to retrieve the information of the request such as the payload) 
  3. BsonValue contentToTransform (the json document to transform)
  4. BsonValue args (the arguments passed via the args property of the transformer metadata object)
  5. BsonValue confArgs (the arguments passed via the args property specified in the configuration file)  
    /**
     * 
     * @param exchange the server exchange
     * @param context the request context
     * @param contentToTransform the content data to transform
     * @param args the args sepcified in the collection metadata via args property
     * @param confArgs the args specified in the configuration file via args property
     * @return true if completed successfully
     */
    default void transform(
            HttpServerExchange exchange,
            RequestContext context,
            BsonValue contentToTransform,
            final BsonValue args,
            BsonValue confArgs)
}

Once a transformer has been implemented, it can be defined in the configuration file.

The following is the default configuration file section declaring the off-the-shelf transformers provided with RESTHeart plus custom one.

    # transformers group used by handlers:
    # org.restheart.handlers.metadata.RequestTransformerMetadataHandler and
    # org.restheart.handlers.metadata.ResponseTransformerMetadataHandler
    # More information in transformers javadoc
    - group: transformers
      interface: org.restheart.metadata.transformers.Transformer
      singletons:
        - name: addRequestProperties
          class: org.restheart.metadata.transformers.RequestPropsInjecterTransformer
        - name: filterProperties
          class: org.restheart.metadata.transformers.FilterTransformer
        - name: stringsToOids
          class: org.restheart.metadata.transformers.ValidOidsStringsAsOidsTransformer
        - name: oidsToStrings
          class: org.restheart.metadata.transformers.OidsAsStringsTransformer
        - name: writeResult
          class: org.restheart.metadata.transformers.WriteResultTransformer
        - name: hashProperties
          class: org.restheart.metadata.transformers.HashTransformer
        - name: myTransformer
          class: com.whatever.MyTransformer
          args:
            msg: "foo bar"
                    
        

Or course, the class of the custom checker must be added to the java classpath. See (How to package custom code)[/docs/v3/custom-code-packaging-howto] for more information.

For example, RESTHeart could be started with the following command:

$ java -server -classpath restheart.jar:custom-transformer.jar org.restheart.Bootstrapper restheart.yml

The following code, is an example transformer that adds to the content the property timestamp.

import org.restheart.metadata.transformers.Transformer;
import io.undertow.server.HttpServerExchange;
import org.restheart.handlers.RequestContext;
import com.mongodb.DBObject;

package com.whatever;

public class MyTransformer implements Transformer {
    tranform(final HttpServerExchange exchange, 
        final RequestContext context, 
        BsonValue contentToTransform, 
        final BsonValue args,
        BsonValue confArgs) {
        contentToTransform.put("msg", confArgs.get("msg"));
        contentToTransform.put("_timestamp", System.currentTimeMillis());
    }
}